feat(operations): add capacity and resilience gates (#18)

This commit is contained in:
KyuubiYoru
2026-07-16 15:57:01 +02:00
parent 08729ae25c
commit 609dad7cf1
21 changed files with 1759 additions and 107 deletions
@@ -0,0 +1,91 @@
namespace FinalFactory.Rendezvous.Capacity;
internal sealed record CapacityOptions
{
public required string Profile { get; init; }
public required int Listings { get; init; }
public required int Attempts { get; init; }
public required int Samples { get; init; }
public required int SoakCycles { get; init; }
public required int SoakSeconds { get; init; }
public string? OutputPath { get; init; }
public static CapacityOptions Parse(string[] args)
{
Dictionary<string, string> values = ParseArguments(args);
string profile = values.GetValueOrDefault("--profile") ?? "quick";
(int listings, int attempts, int samples, int soakCycles, int soakSeconds) = profile switch
{
"quick" => (1_000, 500, 100, 20, 0),
"candidate" => (25_000, 10_000, 1_000, 1_000, 300),
_ => throw new ArgumentException("--profile must be 'quick' or 'candidate'."),
};
return new()
{
Profile = profile,
Listings = ParsePositive(values.GetValueOrDefault("--listings"), listings, "--listings"),
Attempts = ParsePositive(values.GetValueOrDefault("--attempts"), attempts, "--attempts"),
Samples = ParsePositive(values.GetValueOrDefault("--samples"), samples, "--samples"),
SoakCycles = ParsePositive(
values.GetValueOrDefault("--soak-cycles"),
soakCycles,
"--soak-cycles"),
SoakSeconds = ParseNonNegative(
values.GetValueOrDefault("--soak-seconds"),
soakSeconds,
"--soak-seconds"),
OutputPath = values.GetValueOrDefault("--output"),
};
}
private static Dictionary<string, string> ParseArguments(string[] args)
{
HashSet<string> allowed =
[
"--profile",
"--listings",
"--attempts",
"--samples",
"--soak-cycles",
"--soak-seconds",
"--output",
];
Dictionary<string, string> values = new(StringComparer.Ordinal);
for (int index = 0; index < args.Length; index += 2)
{
string option = args[index];
if (!allowed.Contains(option))
{
throw new ArgumentException($"Unknown option: {option}.");
}
if (index == args.Length - 1
|| args[index + 1].StartsWith("--", StringComparison.Ordinal))
{
throw new ArgumentException($"{option} requires a value.");
}
if (!values.TryAdd(option, args[index + 1]))
{
throw new ArgumentException($"{option} may be supplied only once.");
}
}
return values;
}
private static int ParsePositive(string? value, int fallback, string option) =>
value is null
? fallback
: int.TryParse(value, out int parsed) && parsed > 0
? parsed
: throw new ArgumentException($"{option} must be a positive integer.");
private static int ParseNonNegative(string? value, int fallback, string option) =>
value is null
? fallback
: int.TryParse(value, out int parsed) && parsed >= 0
? parsed
: throw new ArgumentException($"{option} must be a non-negative integer.");
}