Skip to content
Closed
Show file tree
Hide file tree
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
56 changes: 56 additions & 0 deletions src/BenchmarkDotNet/Analysers/LoggerAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Reports;
using System;
using System.Collections.Generic;

namespace BenchmarkDotNet.Analysers
{
public class LoggerAnalyzer : IAnalyser, ILogger
{
private int errorCount;
private int warningCount;
public static readonly LoggerAnalyzer Instance = new LoggerAnalyzer();

private LoggerAnalyzer()
{

}

public string Id => nameof(LoggerAnalyzer);

public int Priority => throw new NotImplementedException();

public IEnumerable<Conclusion> Analyse(Summary summary)
{
if (errorCount > 0)
{
yield return Conclusion.CreateError(Id, "There is one or more errors. See log for dettails.");
}
if (warningCount > 0)
{
yield return Conclusion.CreateWarning(Id, "There is one or more warning. See log for details.");
}
}

public void Flush() { }

public void Write(LogKind logKind, string text)
{
switch (logKind)
{
case LogKind.Error:
errorCount++;
break;
case LogKind.Warning:
warningCount++;
break;
default:
break;
}
}

public void WriteLine() { }

public void WriteLine(LogKind logKind, string text) => Write(logKind, text);
}
}
2 changes: 2 additions & 0 deletions src/BenchmarkDotNet/Configs/DefaultConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public IEnumerable<ILogger> GetLoggers()
yield return LinqPadLogger.Instance;
else
yield return ConsoleLogger.Default;
yield return LoggerAnalyzer.Instance;
}

public IEnumerable<IAnalyser> GetAnalysers()
Expand All @@ -53,6 +54,7 @@ public IEnumerable<IAnalyser> GetAnalysers()
yield return RuntimeErrorAnalyser.Default;
yield return ZeroMeasurementAnalyser.Default;
yield return BaselineCustomAnalyzer.Default;
yield return LoggerAnalyzer.Instance;
}

public IEnumerable<IValidator> GetValidators()
Expand Down
72 changes: 63 additions & 9 deletions src/BenchmarkDotNet/Configs/ImmutableConfigBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public static ImmutableConfig Create(IConfig source)

var uniqueHardwareCounters = source.GetHardwareCounters().ToImmutableHashSet();
var uniqueDiagnosers = GetDiagnosers(source.GetDiagnosers(), uniqueHardwareCounters);
var uniqueExporters = GetExporters(source.GetExporters(), uniqueDiagnosers);
var uniqueExporters = GetExporters(source.GetExporters(), uniqueDiagnosers, uniqueLoggers);
var uniqueAnalyzers = GetAnalysers(source.GetAnalysers(), uniqueDiagnosers);

var uniqueValidators = GetValidators(source.GetValidators(), MandatoryValidators, source.Options);
Expand Down Expand Up @@ -88,18 +88,49 @@ private static ImmutableHashSet<IDiagnoser> GetDiagnosers(IEnumerable<IDiagnoser
return builder.ToImmutable();
}

private static ImmutableArray<IExporter> GetExporters(IEnumerable<IExporter> exporters, ImmutableHashSet<IDiagnoser> uniqueDiagnosers)
private static ImmutableArray<IExporter> GetExporters(IEnumerable<IExporter> exporters,
ImmutableHashSet<IDiagnoser> uniqueDiagnosers,
IEnumerable<Loggers.ILogger> loggers = null)
{
var result = new List<IExporter>();

void PrintWarning(string message)
{
if (loggers?.Any() == true)
{
foreach (var logger in loggers)
{
logger.WriteLine(Loggers.LogKind.Warning,
message);
}
}
}

var mergeDictionary = new Dictionary<System.Type, IExporter>();

foreach (var exporter in exporters)
if (!result.Contains(exporter))
result.Add(exporter);
{
var exporterType = exporter.GetType();
if (mergeDictionary.ContainsKey(exporterType))
{
PrintWarning($"The exporter {exporterType} is already present in configuration. There may be unexpected results.");
}
mergeDictionary[exporterType] = exporter;
}


foreach (var diagnoser in uniqueDiagnosers)
foreach (var exporter in diagnoser.Exporters)
if (!result.Contains(exporter))
result.Add(exporter);
foreach (var exporter in diagnoser.Exporters)
{
var exporterType = exporter.GetType();
if (mergeDictionary.ContainsKey(exporterType))
{
PrintWarning($"The exporter {exporterType} of {diagnoser.GetType().Name} is already present in configuration. There may be unexpected results.");
}
mergeDictionary[exporterType] = exporter;
};

var result = mergeDictionary.Values.ToList();


var hardwareCounterDiagnoser = uniqueDiagnosers.OfType<IHardwareCountersDiagnoser>().SingleOrDefault();
var disassemblyDiagnoser = uniqueDiagnosers.OfType<DisassemblyDiagnoser>().SingleOrDefault();
Expand All @@ -111,8 +142,31 @@ private static ImmutableArray<IExporter> GetExporters(IEnumerable<IExporter> exp
for (int i = result.Count - 1; i >=0; i--)
if (result[i] is IExporterDependencies exporterDependencies)
foreach (var dependency in exporterDependencies.Dependencies)
if (!result.Contains(dependency))
/*
* When exporter that depends on an other already present in the configuration print warning.
*
* Example:
*
* // Global Current Culture separator is Semicolon;
* [CsvMeasurementsExporter(CsvSeparator.Comma)] // force use Comma
* [RPlotExporter]
* public class MyBanch
* {
*
* }
*
* RPlotExporter is depend from CsvMeasurementsExporter.Default
*
* On active logger will by print:
* "The CsvMeasurementsExporter is already present in the configuration. There may be unexpected results of RPlotExporter.
*
*/
if (!result.Any(exporter=> exporter.GetType() == dependency.GetType()))
result.Insert(i, dependency); // All the exporter dependencies should be added before the exporter
else
{
PrintWarning($"The {dependency.Name} is already present in the configuration. There may be unexpected results of {result[i].GetType().Name}.");
}

result.Sort((left, right) => (left is IExporterDependencies).CompareTo(right is IExporterDependencies)); // the case when they were defined by user in wrong order ;)

Expand Down
3 changes: 2 additions & 1 deletion src/BenchmarkDotNet/Loggers/ConsoleLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ private static Dictionary<LogKind, ConsoleColor> CreateColorfulScheme() =>
{ LogKind.Statistic, ConsoleColor.Cyan },
{ LogKind.Info, ConsoleColor.DarkYellow },
{ LogKind.Error, ConsoleColor.Red },
{ LogKind.Hint, ConsoleColor.DarkCyan }
{ LogKind.Hint, ConsoleColor.DarkCyan },
{ LogKind.Warning, ConsoleColor.Yellow }
};

[PublicAPI]
Expand Down
6 changes: 4 additions & 2 deletions src/BenchmarkDotNet/Loggers/LinqPadLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ private static IReadOnlyDictionary<LogKind, string> CreateDarkScheme() =>
{ LogKind.Statistic, "#00FFFF" },
{ LogKind.Info, "#808000" },
{ LogKind.Error, "#FF0000" },
{ LogKind.Hint, "#008080" }
{ LogKind.Hint, "#008080" },
{ LogKind.Warning, "#FFFF00" },
};

private static IReadOnlyDictionary<LogKind, string> CreateLightScheme() =>
Expand All @@ -95,7 +96,8 @@ private static IReadOnlyDictionary<LogKind, string> CreateLightScheme() =>
{ LogKind.Statistic, "#008080" },
{ LogKind.Info, "#808000" },
{ LogKind.Error, "#FF0000" },
{ LogKind.Hint, "#008080" }
{ LogKind.Hint, "#008080" },
{ LogKind.Warning, "#FFFF00" },
};
}
}
2 changes: 1 addition & 1 deletion src/BenchmarkDotNet/Loggers/LogKind.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
{
public enum LogKind
{
Default, Help, Header, Result, Statistic, Info, Error, Hint
Default, Help, Header, Result, Statistic, Info, Error, Hint, Warning
}
}
2 changes: 1 addition & 1 deletion src/BenchmarkDotNet/Running/BenchmarkConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ private static ImmutableConfig GetFullTypeConfig(Type type, IConfig config)
var typeAttributes = type.GetCustomAttributes(true).OfType<IConfigSource>();
var assemblyAttributes = type.Assembly.GetCustomAttributes().OfType<IConfigSource>();

foreach (var configFromAttribute in typeAttributes.Concat(assemblyAttributes))
foreach (var configFromAttribute in assemblyAttributes.Concat(typeAttributes))
config = ManualConfig.Union(config, configFromAttribute.Config);

return ImmutableConfigBuilder.Create(config);
Expand Down
42 changes: 42 additions & 0 deletions tests/BenchmarkDotNet.Tests/Configs/ImmutableConfigTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -331,5 +331,47 @@ public class TestExporterDependency : IExporter
public string Name => nameof(TestExporterDependency);
public void ExportToLog(Summary summary, ILogger logger) { }
}

[Fact]
public void GenerateWarningWhenExporterDependencyAlreadyExistInConfig()
{
System.Globalization.CultureInfo currentCulture = default;
System.Globalization.CultureInfo currentUICulture = default;
{
var ct = System.Threading.Thread.CurrentThread;
currentCulture = ct.CurrentCulture;
currentUICulture = ct.CurrentUICulture;
ct.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;
ct.CurrentUICulture = System.Globalization.CultureInfo.InvariantCulture;
}
try
{
var countWarning = 0;
var logger = new Loggers.DelegateLogger((kind, text) =>
{
if (kind == LogKind.Warning)
{
countWarning++;
}
});

var mutable = ManualConfig.CreateEmpty();
mutable.AddLogger(logger);
mutable.AddExporter(new BenchmarkDotNet.Exporters.Csv.CsvMeasurementsExporter(BenchmarkDotNet.Exporters.Csv.CsvSeparator.Comma));
mutable.AddExporter(RPlotExporter.Default);

var final = ImmutableConfigBuilder.Create(mutable);

Assert.Equal(1, countWarning);
}
finally
{
var ct = System.Threading.Thread.CurrentThread;
ct.CurrentCulture = currentCulture;
ct.CurrentUICulture = currentUICulture;

}

}
}
}
28 changes: 28 additions & 0 deletions tests/BenchmarkDotNet.Tests/Loggers/DelegateLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using BenchmarkDotNet.Loggers;
using System;

namespace BenchmarkDotNet.Tests.Loggers
{
internal class DelegateLogger : ILogger
{
private readonly Action<LogKind, string> writeAction;

public DelegateLogger(Action<LogKind, string> writeAction)
{
this.writeAction = writeAction;
}

public string Id { get; }
public int Priority => 0;

public virtual void Write(LogKind logKind, string text)
=> writeAction?.Invoke(logKind, text);

public virtual void WriteLine() { }

public virtual void WriteLine(LogKind logKind, string text)
=> writeAction?.Invoke(logKind, string.Empty);

public void Flush() { }
}
}