diff --git a/samples/BenchmarkDotNet.Samples/IntroIntegratedExporter.cs b/samples/BenchmarkDotNet.Samples/IntroIntegratedExporter.cs new file mode 100644 index 0000000000..b617edc1b4 --- /dev/null +++ b/samples/BenchmarkDotNet.Samples/IntroIntegratedExporter.cs @@ -0,0 +1,31 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes.Exporters; +using BenchmarkDotNet.Exporters.IntegratedExporter; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BenchmarkDotNet.Samples +{ + [IntegratedExporter(IntegratedExportType.HtmlExporterWithRPlotExporter)] + public class IntroIntegratedExporter + { + [Benchmark] + public void Benchmark() + { + var result = Calculate(); + } + + private int Calculate() + { + int sum = 0; + for (int i = 0; i < 1000; i++) + { + sum += i; + } + return sum; + } + } +} diff --git a/samples/BenchmarkDotNet.Samples/IntroRPlot.cs b/samples/BenchmarkDotNet.Samples/IntroRPlot.cs new file mode 100644 index 0000000000..6e6b258038 --- /dev/null +++ b/samples/BenchmarkDotNet.Samples/IntroRPlot.cs @@ -0,0 +1,39 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Exporters; + + +namespace BenchmarkDotNet.Samples +{ + //[MemoryDiagnoser] + //[Config(typeof(Config))] + [RPlotExporter] + public class IntroRPlot + { + [Benchmark] + public void Benchmark() + { + var result = Calculate(); + } + + private int Calculate() + { + int sum = 0; + for (int i = 0; i < 1000; i++) + { + sum += i; + } + return sum; + } + } + + public class Config : ManualConfig + { + public Config() + { + //AddExporter(CsvMeasurementsExporter.Default); + //AddExporter(RPlotExporter.Default); + } + } + +} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Attributes/Exporters/IntegratedExporterAttribute.cs b/src/BenchmarkDotNet/Attributes/Exporters/IntegratedExporterAttribute.cs new file mode 100644 index 0000000000..407b232435 --- /dev/null +++ b/src/BenchmarkDotNet/Attributes/Exporters/IntegratedExporterAttribute.cs @@ -0,0 +1,17 @@ +using BenchmarkDotNet.Exporters.IntegratedExporter; +using System; +using System.Collections.Generic; +using System.Text; + +namespace BenchmarkDotNet.Attributes.Exporters +{ + public class IntegratedExporterAttribute : IntegratedExporterConfigBaseAttribute + { + protected IntegratedExporterAttribute() + { } + + public IntegratedExporterAttribute(IntegratedExportType integratedExporterType) : base(integratedExporterType) + { + } + } +} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Attributes/Exporters/IntegratedExporterConfigBaseAttribute.cs b/src/BenchmarkDotNet/Attributes/Exporters/IntegratedExporterConfigBaseAttribute.cs new file mode 100644 index 0000000000..155641a70b --- /dev/null +++ b/src/BenchmarkDotNet/Attributes/Exporters/IntegratedExporterConfigBaseAttribute.cs @@ -0,0 +1,25 @@ +using System; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Exporters.IntegratedExporter; +using JetBrains.Annotations; + +namespace BenchmarkDotNet.Attributes +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Assembly)] + public class IntegratedExporterConfigBaseAttribute : Attribute, IConfigSource + { + // CLS-Compliant Code requires a constructor without an array in the argument list + [PublicAPI] + protected IntegratedExporterConfigBaseAttribute() + { + Config = ManualConfig.CreateEmpty(); + } + + protected IntegratedExporterConfigBaseAttribute(IntegratedExportType exporter) + { + Config = ManualConfig.CreateEmpty().SetIntegratedExporterType(exporter); + } + + public IConfig Config { get; } + } +} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Attributes/Exporters/RPlotExporterAttribute.cs b/src/BenchmarkDotNet/Attributes/Exporters/RPlotExporterAttribute.cs index 4b4f6d9c39..9909559e35 100644 --- a/src/BenchmarkDotNet/Attributes/Exporters/RPlotExporterAttribute.cs +++ b/src/BenchmarkDotNet/Attributes/Exporters/RPlotExporterAttribute.cs @@ -1,10 +1,13 @@ using BenchmarkDotNet.Exporters; +using System; +using System.Collections.Generic; namespace BenchmarkDotNet.Attributes { public class RPlotExporterAttribute : ExporterConfigBaseAttribute { - public RPlotExporterAttribute() : base(DefaultExporters.RPlot) + + public RPlotExporterAttribute() : base(new RPlotExporter()) { } } diff --git a/src/BenchmarkDotNet/Configs/ConfigExtensions.cs b/src/BenchmarkDotNet/Configs/ConfigExtensions.cs index 5177227c2a..ec7f8e31a5 100644 --- a/src/BenchmarkDotNet/Configs/ConfigExtensions.cs +++ b/src/BenchmarkDotNet/Configs/ConfigExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel; using System.Globalization; @@ -36,6 +37,7 @@ public static class ConfigExtensions [Obsolete("This method will soon be removed, please start using .AddExporter() instead.")] [EditorBrowsable(EditorBrowsableState.Never)] public static IConfig With(this IConfig config, params IExporter[] exporters) => config.AddExporter(exporters); [PublicAPI] public static ManualConfig AddExporter(this IConfig config, params IExporter[] exporters) => config.With(m => m.AddExporter(exporters)); + [PublicAPI] public static ManualConfig AddIntegratedExporter(this IConfig config, List? dependencies, IExporter withExporter, IExporter exporter) => config.With(m => m.AddIntegratedExporter(dependencies, withExporter, exporter)); [Obsolete("This method will soon be removed, please start using .AddDiagnoser() instead.")] [EditorBrowsable(EditorBrowsableState.Never)] public static IConfig With(this IConfig config, params IDiagnoser[] diagnosers) => config.AddDiagnoser(diagnosers); diff --git a/src/BenchmarkDotNet/Configs/DebugConfig.cs b/src/BenchmarkDotNet/Configs/DebugConfig.cs index 0fdda7b08d..6a11383d13 100644 --- a/src/BenchmarkDotNet/Configs/DebugConfig.cs +++ b/src/BenchmarkDotNet/Configs/DebugConfig.cs @@ -6,6 +6,7 @@ using BenchmarkDotNet.Diagnosers; using BenchmarkDotNet.EventProcessors; using BenchmarkDotNet.Exporters; +using BenchmarkDotNet.Exporters.IntegratedExporter; using BenchmarkDotNet.Filters; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Loggers; @@ -58,6 +59,8 @@ public abstract class DebugConfig : IConfig public IEnumerable GetValidators() => Array.Empty(); public IEnumerable GetColumnProviders() => DefaultColumnProviders.Instance; public IEnumerable GetExporters() => Array.Empty(); + public IEnumerable GetIntegratedExporters() => Array.Empty(); + public IIntegratedExporter GetIntegratedExporter() => null; public IEnumerable GetLoggers() => new[] { ConsoleLogger.Default }; public IEnumerable GetDiagnosers() => Array.Empty(); public IEnumerable GetAnalysers() => Array.Empty(); diff --git a/src/BenchmarkDotNet/Configs/DefaultConfig.cs b/src/BenchmarkDotNet/Configs/DefaultConfig.cs index b70333cc1d..291f462dcd 100644 --- a/src/BenchmarkDotNet/Configs/DefaultConfig.cs +++ b/src/BenchmarkDotNet/Configs/DefaultConfig.cs @@ -9,6 +9,7 @@ using BenchmarkDotNet.EventProcessors; using BenchmarkDotNet.Exporters; using BenchmarkDotNet.Exporters.Csv; +using BenchmarkDotNet.Exporters.IntegratedExporter; using BenchmarkDotNet.Filters; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Loggers; @@ -40,6 +41,10 @@ public IEnumerable GetExporters() yield return HtmlExporter.Default; } + public IEnumerable GetIntegratedExporters() => Array.Empty(); + + public IIntegratedExporter GetIntegratedExporter() => null; + public IEnumerable GetLoggers() { if (LinqPadLogger.IsAvailable) @@ -77,7 +82,7 @@ public IEnumerable GetValidators() public IOrderer Orderer => null; public ICategoryDiscoverer? CategoryDiscoverer => null; - public ConfigUnionRule UnionRule => ConfigUnionRule.Union; + public ConfigUnionRule UnionRule { get; set; } = ConfigUnionRule.Union; public CultureInfo CultureInfo => null; @@ -113,5 +118,7 @@ public string ArtifactsPath public IEnumerable GetEventProcessors() => Array.Empty(); public IEnumerable GetColumnHidingRules() => Array.Empty(); + + } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Configs/IConfig.cs b/src/BenchmarkDotNet/Configs/IConfig.cs index b311c235f5..94965960b9 100644 --- a/src/BenchmarkDotNet/Configs/IConfig.cs +++ b/src/BenchmarkDotNet/Configs/IConfig.cs @@ -6,6 +6,7 @@ using BenchmarkDotNet.Diagnosers; using BenchmarkDotNet.EventProcessors; using BenchmarkDotNet.Exporters; +using BenchmarkDotNet.Exporters.IntegratedExporter; using BenchmarkDotNet.Filters; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Loggers; @@ -20,6 +21,7 @@ public interface IConfig { IEnumerable GetColumnProviders(); IEnumerable GetExporters(); + IEnumerable GetIntegratedExporters(); IEnumerable GetLoggers(); IEnumerable GetDiagnosers(); IEnumerable GetAnalysers(); diff --git a/src/BenchmarkDotNet/Configs/ImmutableConfig.cs b/src/BenchmarkDotNet/Configs/ImmutableConfig.cs index b6e03126fd..73bce58ff0 100644 --- a/src/BenchmarkDotNet/Configs/ImmutableConfig.cs +++ b/src/BenchmarkDotNet/Configs/ImmutableConfig.cs @@ -8,6 +8,7 @@ using BenchmarkDotNet.Diagnosers; using BenchmarkDotNet.EventProcessors; using BenchmarkDotNet.Exporters; +using BenchmarkDotNet.Exporters.IntegratedExporter; using BenchmarkDotNet.Filters; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Loggers; @@ -24,6 +25,7 @@ public sealed class ImmutableConfig : IConfig // if something is an array here instead of hashset it means it must have a guaranteed order of elements private readonly ImmutableArray columnProviders; private readonly ImmutableArray exporters; + private readonly ImmutableArray integratedExporters; private readonly ImmutableHashSet loggers; private readonly ImmutableHashSet diagnosers; private readonly ImmutableHashSet analysers; @@ -41,6 +43,7 @@ internal ImmutableConfig( ImmutableHashSet uniqueHardwareCounters, ImmutableHashSet uniqueDiagnosers, ImmutableArray uniqueExporters, + ImmutableArray uniqueIntegratedExporters, ImmutableHashSet uniqueAnalyzers, ImmutableHashSet uniqueValidators, ImmutableHashSet uniqueFilters, @@ -63,6 +66,7 @@ internal ImmutableConfig( hardwareCounters = uniqueHardwareCounters; diagnosers = uniqueDiagnosers; exporters = uniqueExporters; + integratedExporters = uniqueIntegratedExporters; analysers = uniqueAnalyzers; validators = uniqueValidators; filters = uniqueFilters; @@ -92,6 +96,7 @@ internal ImmutableConfig( public IEnumerable GetColumnProviders() => columnProviders; public IEnumerable GetExporters() => exporters; + public IEnumerable GetIntegratedExporters() => integratedExporters; public IEnumerable GetLoggers() => loggers; public IEnumerable GetDiagnosers() => diagnosers; public IEnumerable GetAnalysers() => analysers; @@ -105,6 +110,7 @@ internal ImmutableConfig( public ILogger GetCompositeLogger() => new CompositeLogger(loggers); public IExporter GetCompositeExporter() => new CompositeExporter(exporters); + public IExporter GetCompositeIntegratedExporter() => new CompositeIntegratedExporter(integratedExporters); public IValidator GetCompositeValidator() => new CompositeValidator(validators); public IAnalyser GetCompositeAnalyser() => new CompositeAnalyser(analysers); public IDiagnoser GetCompositeDiagnoser() => new CompositeDiagnoser(diagnosers); diff --git a/src/BenchmarkDotNet/Configs/ImmutableConfigBuilder.cs b/src/BenchmarkDotNet/Configs/ImmutableConfigBuilder.cs index d7c0b5eb0f..0e0022cfe3 100644 --- a/src/BenchmarkDotNet/Configs/ImmutableConfigBuilder.cs +++ b/src/BenchmarkDotNet/Configs/ImmutableConfigBuilder.cs @@ -1,9 +1,11 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Reflection; using BenchmarkDotNet.Analysers; using BenchmarkDotNet.Diagnosers; using BenchmarkDotNet.Exporters; +using BenchmarkDotNet.Exporters.IntegratedExporter; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Order; using BenchmarkDotNet.Reports; @@ -43,7 +45,28 @@ public static ImmutableConfig Create(IConfig source) var uniqueHardwareCounters = source.GetHardwareCounters().Where(counter => counter != HardwareCounter.NotSet).ToImmutableHashSet(); var uniqueDiagnosers = GetDiagnosers(source.GetDiagnosers(), uniqueHardwareCounters); - var uniqueExporters = GetExporters(source.GetExporters(), uniqueDiagnosers, configAnalyse); + + var integratedExporters = source?.GetIntegratedExporters(); + IEnumerable? distinctExporters = null; + + if (integratedExporters?.Any() == true) + { + distinctExporters = source.GetExporters() + .Where(e => + !integratedExporters.Any(ie => + ie.Exporter.GetType() == e.GetType() || + ie.WithExporter.GetType() == e.GetType() || + ie.Dependencies?.Any(d => d.GetType() == e.GetType()) == true)) + .ToList(); + } + else + { + distinctExporters = source.GetExporters(); + } + + var allUniqueExporters = GetExporters(distinctExporters, uniqueDiagnosers, configAnalyse); + + // Check for integrated exporters var uniqueAnalyzers = GetAnalysers(source.GetAnalysers(), uniqueDiagnosers); var uniqueValidators = GetValidators(source.GetValidators(), MandatoryValidators, source.Options); @@ -60,7 +83,8 @@ public static ImmutableConfig Create(IConfig source) uniqueLoggers, uniqueHardwareCounters, uniqueDiagnosers, - uniqueExporters, + allUniqueExporters, + integratedExporters?.ToImmutableArray() ?? new ImmutableArray(), uniqueAnalyzers, uniqueValidators, uniqueFilters, diff --git a/src/BenchmarkDotNet/Configs/ManualConfig.cs b/src/BenchmarkDotNet/Configs/ManualConfig.cs index 5ea1be24e9..5110dde0cb 100644 --- a/src/BenchmarkDotNet/Configs/ManualConfig.cs +++ b/src/BenchmarkDotNet/Configs/ManualConfig.cs @@ -3,11 +3,13 @@ using System.ComponentModel; using System.Globalization; using System.Linq; +using System.Reflection.Metadata; using BenchmarkDotNet.Analysers; using BenchmarkDotNet.Columns; using BenchmarkDotNet.Diagnosers; using BenchmarkDotNet.EventProcessors; using BenchmarkDotNet.Exporters; +using BenchmarkDotNet.Exporters.IntegratedExporter; using BenchmarkDotNet.Extensions; using BenchmarkDotNet.Filters; using BenchmarkDotNet.Jobs; @@ -23,9 +25,7 @@ namespace BenchmarkDotNet.Configs public class ManualConfig : IConfig { private readonly static Conclusion[] emptyConclusion = Array.Empty(); - private readonly List columnProviders = new List(); - private readonly List exporters = new List(); private readonly List loggers = new List(); private readonly List diagnosers = new List(); private readonly List analysers = new List(); @@ -37,8 +37,15 @@ public class ManualConfig : IConfig private readonly List eventProcessors = new List(); private readonly List columnHidingRules = new List(); + private List exporters = new List(); + private List integratedExporters = new List(); + + private IntegratedExportType integratedExporterType; + public IntegratedExportType IntegratedExportType => integratedExporterType; + public IEnumerable GetColumnProviders() => columnProviders; public IEnumerable GetExporters() => exporters; + public IEnumerable GetIntegratedExporters() => integratedExporters; public IEnumerable GetLoggers() => loggers; public IEnumerable GetDiagnosers() => diagnosers; public IEnumerable GetAnalysers() => analysers; @@ -139,6 +146,19 @@ public ManualConfig AddExporter(params IExporter[] newExporters) return this; } + public ManualConfig AddIntegratedExporter(List? dependencies, IExporter withExporter, IExporter exporter) + { + integratedExporters.Add(new IntegratedExporterData() { Exporter = exporter, WithExporter = withExporter, Dependencies = dependencies }); + return this; + } + + public ManualConfig SetIntegratedExporterType(IntegratedExportType type) + { + integratedExporterType = type; + return this; + } + + [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("This method will soon be removed, please start using .AddLogger() instead.")] public void Add(params ILogger[] newLoggers) => AddLogger(newLoggers); @@ -256,6 +276,7 @@ public void Add(IConfig config) { columnProviders.AddRange(config.GetColumnProviders()); exporters.AddRange(config.GetExporters()); + integratedExporters.AddRange(config.GetIntegratedExporters()); loggers.AddRange(config.GetLoggers()); diagnosers.AddRange(config.GetDiagnosers()); analysers.AddRange(config.GetAnalysers()); @@ -319,6 +340,8 @@ public static ManualConfig Union(IConfig globalConfig, IConfig localConfig) return manualConfig; } + + internal void RemoveAllJobs() => jobs.Clear(); internal void RemoveAllDiagnosers() => diagnosers.Clear(); @@ -327,5 +350,6 @@ private static TimeSpan GetBuildTimeout(TimeSpan current, TimeSpan other) => current == DefaultConfig.Instance.BuildTimeout ? other : TimeSpan.FromMilliseconds(Math.Max(current.TotalMilliseconds, other.TotalMilliseconds)); + } } diff --git a/src/BenchmarkDotNet/Exporters/CompositeExporter.cs b/src/BenchmarkDotNet/Exporters/CompositeExporter.cs index 95d3bde9d3..f7da35e021 100644 --- a/src/BenchmarkDotNet/Exporters/CompositeExporter.cs +++ b/src/BenchmarkDotNet/Exporters/CompositeExporter.cs @@ -35,7 +35,8 @@ public void ExportToLog(Summary summary, ILogger logger) } public IEnumerable ExportToFiles(Summary summary, ILogger consoleLogger) - => exporters.SelectMany(exporter => + { + return exporters.SelectMany(exporter => { var files = new List(); try @@ -48,5 +49,6 @@ public IEnumerable ExportToFiles(Summary summary, ILogger consoleLogger) } return files; }); + } } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Exporters/ExporterBase.cs b/src/BenchmarkDotNet/Exporters/ExporterBase.cs index 17a8d432a6..527798b2ae 100644 --- a/src/BenchmarkDotNet/Exporters/ExporterBase.cs +++ b/src/BenchmarkDotNet/Exporters/ExporterBase.cs @@ -54,7 +54,7 @@ internal string GetArtifactFullName(Summary summary) return $"{Path.Combine(summary.ResultsDirectoryPath, fileName)}-{FileCaption}{FileNameSuffix}.{FileExtension}"; } - private static string GetFileName(Summary summary) + protected static string GetFileName(Summary summary) { // we can't use simple name here, because user might be running benchmarks for a library, which defines few types with the same name // and reports the results per type, so every summary is going to contain just single benchmark diff --git a/src/BenchmarkDotNet/Exporters/HtmlExporter.cs b/src/BenchmarkDotNet/Exporters/HtmlExporter.cs index 8fe3068e14..d240c8964e 100644 --- a/src/BenchmarkDotNet/Exporters/HtmlExporter.cs +++ b/src/BenchmarkDotNet/Exporters/HtmlExporter.cs @@ -1,10 +1,13 @@ -using BenchmarkDotNet.Extensions; +using BenchmarkDotNet.Exporters.IntegratedExporter; +using BenchmarkDotNet.Extensions; using BenchmarkDotNet.Loggers; using BenchmarkDotNet.Reports; +using System; +using System.Collections.Generic; namespace BenchmarkDotNet.Exporters { - public class HtmlExporter : ExporterBase + public class HtmlExporter : IntegratedExporterExtension { private const string CssDefinition = @"