Skip to content

Commit

Permalink
Introduce CategoryDiscoverer, fix #2306
Browse files Browse the repository at this point in the history
  • Loading branch information
AndreyAkinshin committed May 14, 2023
1 parent 9602893 commit 521b7fd
Show file tree
Hide file tree
Showing 14 changed files with 266 additions and 41 deletions.
26 changes: 26 additions & 0 deletions docs/articles/samples/IntroCategoryDiscoverer.md
@@ -0,0 +1,26 @@
---
uid: BenchmarkDotNet.Samples.IntroCategoryDiscoverer
---

## Sample: IntroCategoryDiscoverer

The category discovery strategy can be overridden using an instance of `ICategoryDiscoverer`.

### Source code

[!code-csharp[IntroCategoryDiscoverer.cs](../../../samples/BenchmarkDotNet.Samples/IntroCategoryDiscoverer.cs)]

### Output

```markdown
| Method | Categories | Mean | Error |
|------- |----------- |---------:|------:|
| Bar | All,B | 126.5 us | NA |
| Foo | All,F | 114.0 us | NA |
```

### Links

* The permanent link to this sample: @BenchmarkDotNet.Samples.IntroCategoryDiscoverer

---
2 changes: 2 additions & 0 deletions docs/articles/samples/toc.yml
Expand Up @@ -12,6 +12,8 @@
href: IntroCategories.md
- name: IntroCategoryBaseline
href: IntroCategoryBaseline.md
- name: IntroCategoryDiscoverer
href: IntroCategoryDiscoverer.md
- name: IntroColdStart
href: IntroColdStart.md
- name: IntroComparableComplexParam
Expand Down
46 changes: 46 additions & 0 deletions samples/BenchmarkDotNet.Samples/IntroCategoryDiscoverer.cs
@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Running;

namespace BenchmarkDotNet.Samples
{
[DryJob]
[CategoriesColumn]
[CustomCategoryDiscoverer]
public class IntroCategoryDiscoverer
{
private class CustomCategoryDiscoverer : DefaultCategoryDiscoverer
{
public override string[] GetCategories(MethodInfo method)
{
var categories = new List<string>();
categories.AddRange(base.GetCategories(method));
categories.Add("All");
categories.Add(method.Name.Substring(0, 1));
return categories.ToArray();
}
}

[AttributeUsage(AttributeTargets.Class)]
private class CustomCategoryDiscovererAttribute : Attribute, IConfigSource
{
public CustomCategoryDiscovererAttribute()
{
Config = ManualConfig.CreateEmpty()
.WithCategoryDiscoverer(new CustomCategoryDiscoverer());
}

public IConfig Config { get; }
}


[Benchmark]
public void Foo() { }

[Benchmark]
public void Bar() { }
}
}
17 changes: 17 additions & 0 deletions src/BenchmarkDotNet/Attributes/CategoryDiscovererAttribute.cs
@@ -0,0 +1,17 @@
using System;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Running;

namespace BenchmarkDotNet.Attributes
{
[AttributeUsage(AttributeTargets.Class)]
public class CategoryDiscovererAttribute : Attribute, IConfigSource
{
public CategoryDiscovererAttribute(bool inherit = true)
{
Config = ManualConfig.CreateEmpty().WithCategoryDiscoverer(new DefaultCategoryDiscoverer(inherit));
}

public IConfig Config { get; }
}
}
2 changes: 2 additions & 0 deletions src/BenchmarkDotNet/Configs/DebugConfig.cs
Expand Up @@ -12,6 +12,7 @@
using BenchmarkDotNet.Order;
using BenchmarkDotNet.Portability;
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Toolchains.InProcess.Emit;
using BenchmarkDotNet.Validators;

Expand Down Expand Up @@ -66,6 +67,7 @@ public abstract class DebugConfig : IConfig
public IEnumerable<IColumnHidingRule> GetColumnHidingRules() => Array.Empty<IColumnHidingRule>();

public IOrderer Orderer => DefaultOrderer.Instance;
public ICategoryDiscoverer? CategoryDiscoverer => DefaultCategoryDiscoverer.Instance;
public SummaryStyle SummaryStyle => SummaryStyle.Default;
public ConfigUnionRule UnionRule => ConfigUnionRule.Union;
public TimeSpan BuildTimeout => DefaultConfig.Instance.BuildTimeout;
Expand Down
2 changes: 2 additions & 0 deletions src/BenchmarkDotNet/Configs/DefaultConfig.cs
Expand Up @@ -13,6 +13,7 @@
using BenchmarkDotNet.Order;
using BenchmarkDotNet.Portability;
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Validators;

namespace BenchmarkDotNet.Configs
Expand Down Expand Up @@ -72,6 +73,7 @@ public IEnumerable<IValidator> GetValidators()
}

public IOrderer Orderer => null;
public ICategoryDiscoverer? CategoryDiscoverer => null;

public ConfigUnionRule UnionRule => ConfigUnionRule.Union;

Expand Down
4 changes: 3 additions & 1 deletion src/BenchmarkDotNet/Configs/IConfig.cs
Expand Up @@ -10,6 +10,7 @@
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Order;
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Validators;
using JetBrains.Annotations;

Expand All @@ -30,6 +31,7 @@ public interface IConfig
IEnumerable<IColumnHidingRule> GetColumnHidingRules();

IOrderer? Orderer { get; }
ICategoryDiscoverer? CategoryDiscoverer { get; }

SummaryStyle SummaryStyle { get; }

Expand Down Expand Up @@ -57,4 +59,4 @@ public interface IConfig
/// </summary>
IReadOnlyList<Conclusion> ConfigAnalysisConclusion { get; }
}
}
}
4 changes: 3 additions & 1 deletion src/BenchmarkDotNet/Configs/ImmutableConfig.cs
Expand Up @@ -14,7 +14,6 @@
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Validators;
using JetBrains.Annotations;
using RunMode = BenchmarkDotNet.Diagnosers.RunMode;

namespace BenchmarkDotNet.Configs
Expand Down Expand Up @@ -50,6 +49,7 @@ public sealed class ImmutableConfig : IConfig
string artifactsPath,
CultureInfo cultureInfo,
IOrderer orderer,
ICategoryDiscoverer categoryDiscoverer,
SummaryStyle summaryStyle,
ConfigOptions options,
TimeSpan buildTimeout,
Expand All @@ -70,6 +70,7 @@ public sealed class ImmutableConfig : IConfig
ArtifactsPath = artifactsPath;
CultureInfo = cultureInfo;
Orderer = orderer;
CategoryDiscoverer = categoryDiscoverer;
SummaryStyle = summaryStyle;
Options = options;
BuildTimeout = buildTimeout;
Expand All @@ -81,6 +82,7 @@ public sealed class ImmutableConfig : IConfig
public CultureInfo CultureInfo { get; }
public ConfigOptions Options { get; }
public IOrderer Orderer { get; }
public ICategoryDiscoverer CategoryDiscoverer { get; }
public SummaryStyle SummaryStyle { get; }
public TimeSpan BuildTimeout { get; }

Expand Down
2 changes: 2 additions & 0 deletions src/BenchmarkDotNet/Configs/ImmutableConfigBuilder.cs
Expand Up @@ -7,6 +7,7 @@
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Order;
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Validators;

namespace BenchmarkDotNet.Configs
Expand Down Expand Up @@ -69,6 +70,7 @@ public static ImmutableConfig Create(IConfig source)
source.ArtifactsPath ?? DefaultConfig.Instance.ArtifactsPath,
source.CultureInfo,
source.Orderer ?? DefaultOrderer.Instance,
source.CategoryDiscoverer ?? DefaultCategoryDiscoverer.Instance,
source.SummaryStyle ?? SummaryStyle.Default,
source.Options,
source.BuildTimeout,
Expand Down
9 changes: 9 additions & 0 deletions src/BenchmarkDotNet/Configs/ManualConfig.cs
Expand Up @@ -13,6 +13,7 @@
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Order;
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Validators;
using JetBrains.Annotations;

Expand Down Expand Up @@ -51,6 +52,7 @@ public class ManualConfig : IConfig
[PublicAPI] public string ArtifactsPath { get; set; }
[PublicAPI] public CultureInfo CultureInfo { get; set; }
[PublicAPI] public IOrderer Orderer { get; set; }
[PublicAPI] public ICategoryDiscoverer CategoryDiscoverer { get; set; }
[PublicAPI] public SummaryStyle SummaryStyle { get; set; }
[PublicAPI] public TimeSpan BuildTimeout { get; set; } = DefaultConfig.Instance.BuildTimeout;

Expand Down Expand Up @@ -92,6 +94,12 @@ public ManualConfig WithOrderer(IOrderer orderer)
return this;
}

public ManualConfig WithCategoryDiscoverer(ICategoryDiscoverer categoryDiscoverer)
{
CategoryDiscoverer = categoryDiscoverer;
return this;
}

public ManualConfig WithBuildTimeout(TimeSpan buildTimeout)
{
BuildTimeout = buildTimeout;
Expand Down Expand Up @@ -247,6 +255,7 @@ public void Add(IConfig config)
hardwareCounters.AddRange(config.GetHardwareCounters());
filters.AddRange(config.GetFilters());
Orderer = config.Orderer ?? Orderer;
CategoryDiscoverer = config.CategoryDiscoverer ?? CategoryDiscoverer;
ArtifactsPath = config.ArtifactsPath ?? ArtifactsPath;
CultureInfo = config.CultureInfo ?? CultureInfo;
SummaryStyle = config.SummaryStyle ?? SummaryStyle;
Expand Down
30 changes: 10 additions & 20 deletions src/BenchmarkDotNet/Running/BenchmarkConverter.cs
Expand Up @@ -51,7 +51,8 @@ private static BenchmarkRunInfo MethodsToBenchmarksWithFullConfig(Type type, Met
var iterationSetupMethods = GetAttributedMethods<IterationSetupAttribute>(allMethods, "IterationSetup");
var iterationCleanupMethods = GetAttributedMethods<IterationCleanupAttribute>(allMethods, "IterationCleanup");

var targets = GetTargets(benchmarkMethods, type, globalSetupMethods, globalCleanupMethods, iterationSetupMethods, iterationCleanupMethods).ToArray();
var targets = GetTargets(benchmarkMethods, type, globalSetupMethods, globalCleanupMethods, iterationSetupMethods, iterationCleanupMethods,
configPerType).ToArray();

var parameterDefinitions = GetParameterDefinitions(type);
var parameterInstancesList = parameterDefinitions.Expand(configPerType.SummaryStyle);
Expand Down Expand Up @@ -115,7 +116,8 @@ private static ImmutableConfig GetFullMethodConfig(MethodInfo method, ImmutableC
Tuple<MethodInfo, TargetedAttribute>[] globalSetupMethods,
Tuple<MethodInfo, TargetedAttribute>[] globalCleanupMethods,
Tuple<MethodInfo, TargetedAttribute>[] iterationSetupMethods,
Tuple<MethodInfo, TargetedAttribute>[] iterationCleanupMethods)
Tuple<MethodInfo, TargetedAttribute>[] iterationCleanupMethods,
IConfig config)
{
return targetMethods
.Select(methodInfo => CreateDescriptor(type,
Expand All @@ -125,7 +127,8 @@ private static ImmutableConfig GetFullMethodConfig(MethodInfo method, ImmutableC
GetTargetedMatchingMethod(methodInfo, iterationSetupMethods),
GetTargetedMatchingMethod(methodInfo, iterationCleanupMethods),
methodInfo.ResolveAttribute<BenchmarkAttribute>(),
targetMethods));
targetMethods,
config));
}

private static MethodInfo GetTargetedMatchingMethod(MethodInfo benchmarkMethod, Tuple<MethodInfo, TargetedAttribute>[] methods)
Expand All @@ -152,8 +155,10 @@ private static MethodInfo GetTargetedMatchingMethod(MethodInfo benchmarkMethod,
MethodInfo iterationSetupMethod,
MethodInfo iterationCleanupMethod,
BenchmarkAttribute attr,
MethodInfo[] targetMethods)
MethodInfo[] targetMethods,
IConfig config)
{
var categoryDiscoverer = config.CategoryDiscoverer ?? DefaultCategoryDiscoverer.Instance;
var target = new Descriptor(
type,
methodInfo,
Expand All @@ -163,7 +168,7 @@ private static MethodInfo GetTargetedMatchingMethod(MethodInfo benchmarkMethod,
iterationCleanupMethod,
attr.Description,
baseline: attr.Baseline,
categories: GetCategories(methodInfo),
categories: categoryDiscoverer.GetCategories(methodInfo),
operationsPerInvoke: attr.OperationsPerInvoke,
methodIndex: Array.IndexOf(targetMethods, methodInfo));
AssertMethodHasCorrectSignature("Benchmark", methodInfo);
Expand Down Expand Up @@ -245,21 +250,6 @@ private static IEnumerable<ParameterInstances> GetArgumentsDefinitions(MethodInf
yield return SmartParamBuilder.CreateForArguments(benchmark, parameterDefinitions, valuesInfo, sourceIndex, summaryStyle);
}

private static string[] GetCategories(MethodInfo method)
{
var attributes = new List<BenchmarkCategoryAttribute>();
attributes.AddRange(method.GetCustomAttributes(typeof(BenchmarkCategoryAttribute), true).OfType<BenchmarkCategoryAttribute>());
var type = method.ReflectedType;
if (type != null)
{
attributes.AddRange(type.GetTypeInfo().GetCustomAttributes(typeof(BenchmarkCategoryAttribute), true).OfType<BenchmarkCategoryAttribute>());
attributes.AddRange(type.GetTypeInfo().Assembly.GetCustomAttributes().OfType<BenchmarkCategoryAttribute>());
}
if (attributes.Count == 0)
return Array.Empty<string>();
return attributes.SelectMany(attr => attr.Categories).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
}

private static ImmutableArray<BenchmarkCase> GetFilteredBenchmarks(IEnumerable<BenchmarkCase> benchmarks, IEnumerable<IFilter> filters)
=> benchmarks.Where(benchmark => filters.All(filter => filter.Predicate(benchmark))).ToImmutableArray();

Expand Down
35 changes: 35 additions & 0 deletions src/BenchmarkDotNet/Running/DefaultCategoryDiscoverer.cs
@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using BenchmarkDotNet.Attributes;

namespace BenchmarkDotNet.Running
{
public class DefaultCategoryDiscoverer : ICategoryDiscoverer
{
public static readonly ICategoryDiscoverer Instance = new DefaultCategoryDiscoverer();

private readonly bool inherit;

public DefaultCategoryDiscoverer(bool inherit = true)
{
this.inherit = inherit;
}

public virtual string[] GetCategories(MethodInfo method)
{
var attributes = new List<BenchmarkCategoryAttribute>();
attributes.AddRange(method.GetCustomAttributes(typeof(BenchmarkCategoryAttribute), inherit).OfType<BenchmarkCategoryAttribute>());
var type = method.ReflectedType;
if (type != null)
{
attributes.AddRange(type.GetTypeInfo().GetCustomAttributes(typeof(BenchmarkCategoryAttribute), inherit).OfType<BenchmarkCategoryAttribute>());
attributes.AddRange(type.GetTypeInfo().Assembly.GetCustomAttributes().OfType<BenchmarkCategoryAttribute>());
}
if (attributes.Count == 0)
return Array.Empty<string>();
return attributes.SelectMany(attr => attr.Categories).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
}
}
}
9 changes: 9 additions & 0 deletions src/BenchmarkDotNet/Running/ICategoryDiscoverer.cs
@@ -0,0 +1,9 @@
using System.Reflection;

namespace BenchmarkDotNet.Running
{
public interface ICategoryDiscoverer
{
string[] GetCategories(MethodInfo method);
}
}

0 comments on commit 521b7fd

Please sign in to comment.