// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;
namespace GFramework.Cqrs.Benchmarks;
///
/// 提供 GFramework.CQRS benchmark 的统一命令行入口。
///
internal static class Program
{
private const string ArtifactsSuffixOption = "--artifacts-suffix";
private const string ArtifactsSuffixEnvironmentVariable = "GFRAMEWORK_CQRS_BENCHMARK_ARTIFACTS_SUFFIX";
private const string ArtifactsPathEnvironmentVariable = "GFRAMEWORK_CQRS_BENCHMARK_ARTIFACTS_PATH";
private const string IsolatedHostEnvironmentVariable = "GFRAMEWORK_CQRS_BENCHMARK_ISOLATED_HOST";
private const string DefaultArtifactsDirectoryName = "BenchmarkDotNet.Artifacts";
private const string IsolatedHostDirectoryName = "host";
///
/// 运行当前程序集中的全部 benchmark。
///
/// 仓库入口参数与透传给 BenchmarkDotNet 的命令行参数。
private static void Main(string[] args)
{
var invocation = ParseInvocation(args);
ConsoleLogger.Default.WriteLine("Running GFramework.Cqrs benchmarks");
if (invocation.RequiresHostIsolation &&
!string.Equals(
Environment.GetEnvironmentVariable(IsolatedHostEnvironmentVariable),
"1",
StringComparison.Ordinal))
{
Environment.Exit(RunFromIsolatedHost(invocation, args));
}
if (invocation.ArtifactsPath is null)
{
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(invocation.BenchmarkDotNetArguments);
return;
}
ConsoleLogger.Default.WriteLine(
$"Using isolated BenchmarkDotNet artifacts path: {invocation.ArtifactsPath}");
BenchmarkSwitcher
.FromAssembly(typeof(Program).Assembly)
.Run(invocation.BenchmarkDotNetArguments, DefaultConfig.Instance.WithArtifactsPath(invocation.ArtifactsPath));
}
///
/// 解析仓库自定义参数,并生成实际传递给 BenchmarkDotNet 的参数与隔离后的 artifacts 路径。
///
/// 当前进程收到的完整命令行参数。
/// 入口解析后的 benchmark 调用选项。
/// 自定义参数缺失值或包含非法路径片段时抛出。
private static BenchmarkInvocation ParseInvocation(string[] args)
{
var benchmarkDotNetArguments = new List(args.Length);
string? commandLineSuffix = null;
for (var index = 0; index < args.Length; index++)
{
var argument = args[index];
if (!string.Equals(argument, ArtifactsSuffixOption, StringComparison.Ordinal))
{
benchmarkDotNetArguments.Add(argument);
continue;
}
if (index == args.Length - 1)
{
throw new ArgumentException(
$"The {ArtifactsSuffixOption} option requires a suffix value.",
nameof(args));
}
if (commandLineSuffix is not null)
{
throw new ArgumentException(
$"The {ArtifactsSuffixOption} option can only be provided once.",
nameof(args));
}
// 剥离仓库自定义参数,避免将它误传给 BenchmarkDotNet 自身的命令行解析器。
commandLineSuffix = args[++index];
}
var artifactsPath = ResolveArtifactsPath(commandLineSuffix);
return new BenchmarkInvocation(
benchmarkDotNetArguments.ToArray(),
commandLineSuffix,
artifactsPath,
artifactsPath is not null);
}
///
/// 将当前 benchmark 入口重启到独立的宿主工作目录,避免多个并发进程共享同一份 auto-generated build 目录。
///
/// 当前入口解析后的 benchmark 调用选项。
/// 原始命令行参数,用于透传给隔离后的宿主进程。
/// 隔离后宿主进程的退出码。
private static int RunFromIsolatedHost(BenchmarkInvocation invocation, string[] originalArgs)
{
var artifactsPath = invocation.ArtifactsPath
?? throw new ArgumentNullException(nameof(invocation), "An isolated benchmark host requires an artifacts path.");
var currentAssemblyPath = typeof(Program).Assembly.Location;
var sourceHostDirectory = AppContext.BaseDirectory;
var isolatedHostDirectory = Path.Combine(artifactsPath, IsolatedHostDirectoryName);
PrepareIsolatedHostDirectory(sourceHostDirectory, isolatedHostDirectory);
var isolatedAssemblyPath = Path.Combine(
isolatedHostDirectory,
Path.GetFileName(currentAssemblyPath));
var startInfo = new ProcessStartInfo("dotnet")
{
WorkingDirectory = isolatedHostDirectory,
UseShellExecute = false
};
startInfo.ArgumentList.Add(isolatedAssemblyPath);
foreach (var argument in originalArgs)
{
startInfo.ArgumentList.Add(argument);
}
startInfo.Environment[IsolatedHostEnvironmentVariable] = "1";
startInfo.Environment[ArtifactsPathEnvironmentVariable] = artifactsPath;
ConsoleLogger.Default.WriteLine(
$"Launching isolated benchmark host in: {isolatedHostDirectory}");
using var process = Process.Start(startInfo) ??
throw new InvalidOperationException("Failed to launch the isolated benchmark host process.");
process.WaitForExit();
return process.ExitCode;
}
///
/// 根据命令行或环境变量中的 suffix 生成当前 benchmark 运行的独立 artifacts 目录。
///
/// 命令行显式提供的 suffix。
/// 隔离后的 artifacts 目录;若未提供 suffix,则返回 。
private static string? ResolveArtifactsPath(string? commandLineSuffix)
{
var explicitArtifactsPath = Environment.GetEnvironmentVariable(ArtifactsPathEnvironmentVariable);
if (!string.IsNullOrWhiteSpace(explicitArtifactsPath))
{
return Path.GetFullPath(explicitArtifactsPath);
}
if (!string.IsNullOrWhiteSpace(commandLineSuffix))
{
var validatedCommandLineSuffix = ValidateArtifactsSuffix(
commandLineSuffix,
ArtifactsSuffixOption);
return Path.GetFullPath(Path.Combine(DefaultArtifactsDirectoryName, validatedCommandLineSuffix));
}
var environmentSuffix = Environment.GetEnvironmentVariable(ArtifactsSuffixEnvironmentVariable);
if (string.IsNullOrWhiteSpace(environmentSuffix))
{
return null;
}
var validatedEnvironmentSuffix = ValidateArtifactsSuffix(
environmentSuffix,
ArtifactsSuffixEnvironmentVariable);
return Path.GetFullPath(Path.Combine(DefaultArtifactsDirectoryName, validatedEnvironmentSuffix));
}
///
/// 校验自定义 suffix,避免路径穿越、分隔符注入或不可移植字符污染 BenchmarkDotNet 的输出目录。
///
/// 待校验的后缀值。
/// 后缀来源名称,用于错误提示。
/// 可安全用于单级目录名的后缀。
/// 当后缀为空或包含未允许字符时抛出。
private static string ValidateArtifactsSuffix(string suffix, string sourceName)
{
var trimmedSuffix = suffix.Trim();
if (trimmedSuffix.Length == 0)
{
throw new ArgumentException(
$"The {sourceName} value must not be empty.",
nameof(suffix));
}
foreach (var character in trimmedSuffix)
{
if (char.IsAsciiLetterOrDigit(character) || character is '.' or '-' or '_')
{
continue;
}
throw new ArgumentException(
$"The {sourceName} value '{trimmedSuffix}' contains unsupported characters. " +
"Only ASCII letters, digits, '.', '-' and '_' are allowed.",
nameof(suffix));
}
return trimmedSuffix;
}
///
/// 将当前 benchmark 宿主输出复制到独立目录,确保并发运行时的 auto-generated benchmark 项目不会写入同一路径。
///
/// 当前 benchmark 宿主输出目录。
/// 当前 suffix 对应的独立宿主目录。
private static void PrepareIsolatedHostDirectory(string sourceHostDirectory, string isolatedHostDirectory)
{
ValidateIsolatedHostDirectory(sourceHostDirectory, isolatedHostDirectory);
Directory.CreateDirectory(isolatedHostDirectory);
CopyDirectoryRecursively(sourceHostDirectory, isolatedHostDirectory);
}
///
/// 拒绝把隔离宿主目录放到当前宿主输出目录内部,避免递归复制把 `host/host/...` 无限扩张。
///
/// 当前 benchmark 宿主输出目录。
/// 目标隔离宿主目录。
///
/// 等于或位于 之内。
///
private static void ValidateIsolatedHostDirectory(string sourceHostDirectory, string isolatedHostDirectory)
{
var normalizedSourceDirectory = Path.TrimEndingDirectorySeparator(Path.GetFullPath(sourceHostDirectory));
var normalizedIsolatedHostDirectory = Path.TrimEndingDirectorySeparator(Path.GetFullPath(isolatedHostDirectory));
if (string.Equals(
normalizedSourceDirectory,
normalizedIsolatedHostDirectory,
StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(
"The isolated benchmark host directory must differ from the current host output directory.");
}
var relativePath = Path.GetRelativePath(normalizedSourceDirectory, normalizedIsolatedHostDirectory);
if (IsCurrentDirectoryOrChild(relativePath))
{
throw new InvalidOperationException(
$"The isolated benchmark host directory '{normalizedIsolatedHostDirectory}' must not be nested inside the current host output directory '{normalizedSourceDirectory}'.");
}
}
///
/// 判断一个相对路径是否仍指向当前目录或其子目录。
///
/// 相对路径。
/// 目标位于当前目录或其子目录时返回 。
private static bool IsCurrentDirectoryOrChild(string relativePath)
{
if (string.IsNullOrEmpty(relativePath) || string.Equals(relativePath, ".", StringComparison.Ordinal))
{
return true;
}
if (Path.IsPathRooted(relativePath))
{
return false;
}
return !string.Equals(relativePath, "..", StringComparison.Ordinal) &&
!relativePath.StartsWith(".." + Path.DirectorySeparatorChar, StringComparison.Ordinal) &&
!relativePath.StartsWith(".." + Path.AltDirectorySeparatorChar, StringComparison.Ordinal);
}
///
/// 递归复制 benchmark 宿主输出目录,覆盖同名文件以支持同一 suffix 的重复运行。
///
/// 源目录。
/// 目标目录。
private static void CopyDirectoryRecursively(string sourceDirectory, string destinationDirectory)
{
foreach (var directory in Directory.GetDirectories(sourceDirectory, "*", SearchOption.AllDirectories))
{
var relativeDirectory = Path.GetRelativePath(sourceDirectory, directory);
Directory.CreateDirectory(Path.Combine(destinationDirectory, relativeDirectory));
}
foreach (var file in Directory.GetFiles(sourceDirectory, "*", SearchOption.AllDirectories))
{
var relativeFile = Path.GetRelativePath(sourceDirectory, file);
var destinationFile = Path.Combine(destinationDirectory, relativeFile);
Directory.CreateDirectory(Path.GetDirectoryName(destinationFile)!);
File.Copy(file, destinationFile, overwrite: true);
}
}
///
/// 表示一次 benchmark 入口调用在剥离仓库自定义参数后的最终配置。
///
/// 实际传递给 BenchmarkDotNet 的命令行参数。
/// 当前运行声明的隔离后缀;若未声明则为 。
/// 本次运行的 artifacts 目录;若未隔离则为 。
/// 本次运行是否需要重启到隔离宿主目录。
private readonly record struct BenchmarkInvocation(
string[] BenchmarkDotNetArguments,
string? ArtifactsSuffix,
string? ArtifactsPath,
bool RequiresHostIsolation);
}