Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .docfx/Dockerfile.docfx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ARG NGINX_VERSION=1.31.0-alpine
ARG NGINX_VERSION=1.31.2-alpine

FROM --platform=$BUILDPLATFORM nginx:${NGINX_VERSION} AS base
RUN rm -rf /usr/share/nginx/html/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
uid: Codebelt.Extensions.BenchmarkDotNet.Console
summary: *content
---
The `Codebelt.Extensions.BenchmarkDotNet.Console` namespace contains types that provide a structured and opinionated console-hosted execution model for `BenchmarkDotNet`.
The `Codebelt.Extensions.BenchmarkDotNet.Console` namespace removes the boilerplate of turning a console application into a BenchmarkDotNet host. Use it when you want a single static call from `Main` to wire up the generic host, register a `BenchmarkContext` for the command-line arguments, register the default `BenchmarkWorkspace` (or your own `IBenchmarkWorkspace` implementation) through `AddBenchmarkWorkspace`, run every discovered benchmark assembly, and then post-process the generated artifacts — all without writing the hosting setup by hand.

Use `BenchmarkProgram.Run` for synchronous benchmark hosts and `BenchmarkProgram.RunAsync` for asynchronous benchmark hosts; both entry points support the default `BenchmarkWorkspace` and custom `IBenchmarkWorkspace` implementations.
Start with `BenchmarkProgram.Run` from your `Main` for a synchronous host, or `BenchmarkProgram.RunAsync` when your entry point is async. Both forward the command-line arguments into a `BenchmarkContext`, resolve the registered `IBenchmarkWorkspace` and `BenchmarkWorkspaceOptions` from the service provider, and then hand the loaded assemblies to `BenchmarkRunner` (when no arguments are supplied) or to `BenchmarkSwitcher` (when selective filtering is required). The generic `Run<TWorkspace>` / `RunAsync<TWorkspace>` overloads let you plug in a custom `IBenchmarkWorkspace` without rewriting the host.

If you need to suppress status messages in Release, register additional services, or surface a different `IHost` lifecycle, you can either rely on the `setup` delegate that mutates the resolved `BenchmarkWorkspaceOptions` or the optional `serviceConfigurator` delegate that mutates the `IServiceCollection` before the host is built.

[!INCLUDE [availability-modern](../../includes/availability-modern.md)]
32 changes: 18 additions & 14 deletions .docfx/api/namespaces/Codebelt.Extensions.BenchmarkDotNet.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
---
uid: Codebelt.Extensions.BenchmarkDotNet
summary: *content
---
The `Codebelt.Extensions.BenchmarkDotNet` namespace contains types that provide a uniform, opinionated, and extensible way of working with `BenchmarkDotNet`.

[!INCLUDE [availability-modern](../../includes/availability-modern.md)]

### Extension Methods

|Type|Ext|Methods|
|--:|:-:|---|
|BenchmarkWorkspaceOptions|⬇️|`ConfigureBenchmarkDotNet`|
|IServiceCollection|⬇️|`AddBenchmarkWorkspace`, `AddBenchmarkWorkspace<TWorkspace>`|
---
uid: Codebelt.Extensions.BenchmarkDotNet
summary: *content
---
The `Codebelt.Extensions.BenchmarkDotNet` namespace solves the recurring friction of running BenchmarkDotNet from a host application: discovery of the benchmark assemblies, the per-TFM and per-build-configuration filtering that comes with that, and the post-run cleanup of the generated report artifacts. Use it when you want a workspace that scans a `tuning` folder, loads every `*.Benchmarks.dll` for the current `Debug|Release` build and target framework moniker, hands the assemblies to a `BenchmarkRunner` / `BenchmarkSwitcher`, and then moves the per-run output out of the per-run `results` directory and into the long-lived `tuning` directory of your repository.

Start with the default registration: add `AddBenchmarkWorkspace` to an `IServiceCollection`, build a service provider, and resolve `IBenchmarkWorkspace`. That returns the built-in `BenchmarkWorkspace` implementation, which already wires up a sensible `ManualConfig` derived from `BenchmarkWorkspaceOptions.Slim` and BenchmarkDotNet's recommended settings. If you need to extend or replace the configuration — adding a job, attaching a custom exporter, tightening the iteration count — call `ConfigureBenchmarkDotNet` on a `BenchmarkWorkspaceOptions` instance to do it fluently. Reach for `AddBenchmarkWorkspace<TWorkspace>` only when you implement `IBenchmarkWorkspace` yourself to override assembly discovery or post-processing.

The two extension surfaces map directly to the two configuration moments you care about. `BenchmarkWorkspaceOptionsExtensions.ConfigureBenchmarkDotNet` lets you mutate the BenchmarkDotNet `IConfig` on an options instance while the fluent `IConfig` API normally forces a manual reassignment. `ServiceCollectionExtensions.AddBenchmarkWorkspace` and `AddBenchmarkWorkspace<TWorkspace>` register the workspace and the resolved options into the DI container so that any consumer — including a console host — can resolve `IBenchmarkWorkspace` and `BenchmarkWorkspaceOptions` straight from the service provider.

[!INCLUDE [availability-modern](../../includes/availability-modern.md)]

### Extension Members

|Type|Ext|Methods|
|--:|:-:|---|
|BenchmarkWorkspaceOptions|⬇️|`ConfigureBenchmarkDotNet`|
|IServiceCollection|⬇️|`AddBenchmarkWorkspace`, `AddBenchmarkWorkspace<TWorkspace>`|
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
uid: Codebelt.Extensions.BenchmarkDotNet.BenchmarkWorkspace
example:
- *content
---
The following example builds a default `BenchmarkWorkspace` against the current repository and walks through the two phases the workspace actually performs: discovering every `*.Benchmarks.dll` under the configured `tuning` folder for the current build configuration and target framework moniker, then moving the per-run `results` directory into the long-lived `tuning` directory once the benchmark run finishes.

```csharp
using System;
using System.IO;
using Codebelt.Extensions.BenchmarkDotNet;

namespace MyBenchmarks;

public static class Program
{
public static void Main()
{
var repositoryPath = Directory.GetCurrentDirectory();
var options = new BenchmarkWorkspaceOptions
{
RepositoryPath = repositoryPath,
TargetFrameworkMoniker = "net10.0"
};

var workspace = new BenchmarkWorkspace(options);

// Discovers and loads every *.Benchmarks.dll that matches
// <RepositoryPath>/<RepositoryTuningFolder>/bin/<Debug|Release>/<TargetFrameworkMoniker>.
var assemblies = workspace.LoadBenchmarkAssemblies();
Console.WriteLine($"Loaded {assemblies.Length} benchmark assemblies from {repositoryPath}.");

// After a benchmark run, PostProcessArtifacts moves the per-run results directory
// into the configured tuning folder and removes the now-empty results directory.
workspace.PostProcessArtifacts();
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
uid: Codebelt.Extensions.BenchmarkDotNet.BenchmarkWorkspaceOptions
example:
- *content
---
The following example configures a `BenchmarkWorkspaceOptions` instance with the repository layout and discovery knobs the workspace will use, then runs the two lifecycle methods that the workspace invokes before the benchmark run (`PostConfigureOptions`) and during construction (`ValidateOptions`). This is the same shape every consumer follows: build an options instance, optionally override one or more defaults, and let the workspace validate the state.

```csharp
using System;
using Codebelt.Extensions.BenchmarkDotNet;

namespace MyBenchmarks;

public static class Program
{
public static void Main()
{
var options = new BenchmarkWorkspaceOptions
{
// pin the workspace to a specific repository layout
RepositoryPath = @"C:\Repos\MyBenchmarkRepo",
RepositoryTuningFolder = "tuning",
RepositoryReportsFolder = "reports",
TargetFrameworkMoniker = "net10.0",
BenchmarkProjectSuffix = "Benchmarks",
AllowDebugBuild = false,
SkipBenchmarksWithReports = true
};

// late-bind the BenchmarkDotNet artifacts path against the configured repository
options.PostConfigureOptions();

// throws InvalidOperationException if any required property is missing or whitespace
options.ValidateOptions();

Console.WriteLine($"ArtifactsPath: {options.Configuration.ArtifactsPath}");
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
uid: Codebelt.Extensions.BenchmarkDotNet.BenchmarkWorkspaceOptionsExtensions
example:
- *content
---
The following example shows `ConfigureBenchmarkDotNet` being used to add a second BenchmarkDotNet job to the default `IConfig` carried by `BenchmarkWorkspaceOptions`. The helper takes care of forcing the default configuration, passing the current `IConfig` to the delegate, and assigning the returned configuration back onto the options instance — which is otherwise awkward because BenchmarkDotNet's `AddJob` / `AddColumn` / `AddDiagnoser` methods return a new configuration object rather than mutating the receiver.

```csharp
using System;
using System.Linq;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using Codebelt.Extensions.BenchmarkDotNet;
using Perfolizer.Horology;

namespace MyBenchmarks;

public static class Program
{
public static void Main()
{
var options = new BenchmarkWorkspaceOptions();

// fluent IConfig mutations normally require explicit reassignment; this helper does it for you
options.ConfigureBenchmarkDotNet(c => c.AddJob(
Job.Default
.WithWarmupCount(2)
.WithIterationTime(TimeInterval.FromMilliseconds(500))
.WithMaxIterationCount(25)
.WithId("LongRunning")));

Console.WriteLine($"Jobs: {string.Join(", ", options.Configuration.GetJobs().Select(j => j.Id))}");
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
uid: Codebelt.Extensions.BenchmarkDotNet.Console.BenchmarkContext
example:
- *content
---
The following example constructs a `BenchmarkContext` from the command-line arguments passed to the entry point. When the host resolves `BenchmarkContext` from the service provider, it inspects `Args.Length` to decide whether to run every benchmark in every loaded assembly (`BenchmarkRunner.Run`) or to forward the args to `BenchmarkSwitcher` for selective execution. A `null` array is normalized to an empty array so downstream code never has to guard against `null`.

```csharp
using System;
using Codebelt.Extensions.BenchmarkDotNet.Console;

namespace MyBenchmarks;

public static class Program
{
public static void Main(string[] args)
{
var context = new BenchmarkContext(args);
// context.Args is the same array passed to Main, or an empty array when args is null
Console.WriteLine($"BenchmarkContext received {context.Args.Length} argument(s).");
foreach (var arg in context.Args)
{
Console.WriteLine($" - {arg}");
}
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
uid: Codebelt.Extensions.BenchmarkDotNet.Console.BenchmarkProgram
example:
- *content
---
The following example shows the typical `Program.cs` of a benchmark host project: the entry point forwards the command-line arguments to `BenchmarkProgram.RunAsync` and supplies a `setup` delegate that customizes the resolved `BenchmarkWorkspaceOptions` before the host is built. `BenchmarkProgram` derives from `Codebelt.Bootstrapper.Console.MinimalConsoleProgram<BenchmarkProgram>`, so the `Main` plumbing — host configuration, service registration, lifecycle — is inherited; the only thing the host project has to write is the call site and the workspace configuration.

```csharp
using System.Threading.Tasks;
using Codebelt.Extensions.BenchmarkDotNet;
using Codebelt.Extensions.BenchmarkDotNet.Console;

namespace MyBenchmarks;

public static class Program
{
// minimal benchmark host: forwards args into the auto-discovered benchmark workspace
public static Task Main(string[] args) => BenchmarkProgram.RunAsync(args, setup: options =>
{
options.BenchmarkProjectSuffix = "MyBench";
options.AllowDebugBuild = false;
options.SkipBenchmarksWithReports = true;
});
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
uid: Codebelt.Extensions.BenchmarkDotNet.ServiceCollectionExtensions
example:
- *content
---
The following example registers a benchmark workspace through the non-generic `AddBenchmarkWorkspace` overload — which defaults to the built-in `BenchmarkWorkspace` implementation — and the generic `AddBenchmarkWorkspace<TWorkspace>` overload — which lets a consumer plug in a custom `IBenchmarkWorkspace` (here, a `FakeWorkspace` that simply returns an empty assembly set). Both overloads register the workspace and the resolved options as singletons, so any consumer can resolve `IBenchmarkWorkspace` and `BenchmarkWorkspaceOptions` straight from the built `IServiceProvider`.

```csharp
using System;
using System.Reflection;
using Codebelt.Extensions.BenchmarkDotNet;
using Microsoft.Extensions.DependencyInjection;

namespace MyBenchmarks;

// a custom workspace registered through the generic overload
public sealed class FakeWorkspace : IBenchmarkWorkspace
{
public Assembly[] LoadBenchmarkAssemblies() => Array.Empty<Assembly>();
public void PostProcessArtifacts() { }
}

public static class Program
{
public static void Main()
{
// default registration: registers BenchmarkWorkspace and the resolved options as singletons
var services = new ServiceCollection();
services.AddBenchmarkWorkspace(setup: o => o.BenchmarkProjectSuffix = "Benchmarks");
using (var provider = services.BuildServiceProvider())
{
var defaultWorkspace = provider.GetRequiredService<IBenchmarkWorkspace>();
var defaultOptions = provider.GetRequiredService<BenchmarkWorkspaceOptions>();
Console.WriteLine($"default: {defaultWorkspace.GetType().Name} / {defaultOptions.BenchmarkProjectSuffix}");
}

// generic registration: any IBenchmarkWorkspace implementation
var typed = new ServiceCollection();
typed.AddBenchmarkWorkspace<FakeWorkspace>(setup: o => o.RepositoryPath = @"C:\Repos\MyRepo");
using (var typedProvider = typed.BuildServiceProvider())
{
var customWorkspace = typedProvider.GetRequiredService<IBenchmarkWorkspace>();
var customOptions = typedProvider.GetRequiredService<BenchmarkWorkspaceOptions>();
Console.WriteLine($"custom: {customWorkspace.GetType().Name} / {customOptions.RepositoryPath}");
}
}
}
```
6 changes: 4 additions & 2 deletions .docfx/docfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@
{
"files": [
"api/**/*.yml",
"api/**/*.md",
"packages/**/*.md",
"toc.yml",
"*.md"
],
"exclude": [
"api/namespaces/**",
"api/types/**",
"bin/**",
"obj/**"
]
Expand Down Expand Up @@ -69,7 +70,8 @@
"overwrite": [
{
"files": [
"api/namespaces/**.md"
"api/namespaces/**/*.md",
"api/types/**/*.md"
],
"exclude": [
"obj/**",
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/ci-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,8 @@ jobs:
security-events: write

deploy:
if: github.event_name != 'pull_request'
# Avoid skipped optional jobs (for example disabled macOS matrix runs) from suppressing deployment.
if: ${{ always() && github.event_name != 'pull_request' && needs.build.result == 'success' && needs.pack.result == 'success' && needs.test_qualitygate.result == 'success' && needs.sonarcloud.result == 'success' && needs.codecov.result == 'success' && needs.codeql.result == 'success' }}
name: call-nuget
needs: [build, pack, test_qualitygate, sonarcloud, codecov, codeql]
uses: codebeltnet/jobs-nuget-push/.github/workflows/default.yml@v3
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
Version: 1.3.1
Availability: .NET 10 and .NET 9

# ALM
- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs)

Version: 1.3.0
Availability: .NET 10 and .NET 9

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
Version: 1.3.1
Availability: .NET 10 and .NET 9

# ALM
- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs)

Version: 1.3.0
Availability: .NET 10 and .NET 9

Expand Down
Loading
Loading