using System.Globalization; namespace StellaOps.Bench.PolicyEngine; internal sealed record ScenarioResult( string Id, string Label, int Iterations, int FindingCount, double MeanMs, double P95Ms, double MaxMs, double MeanThroughputPerSecond, double MinThroughputPerSecond, double MaxAllocatedMb, double? ThresholdMs, double? MinThroughputThresholdPerSecond, double? MaxAllocatedThresholdMb) { public string IdColumn => Id.Length <= 28 ? Id.PadRight(28) : Id[..28]; public string FindingsColumn => FindingCount.ToString("N0", CultureInfo.InvariantCulture).PadLeft(12); public string MeanColumn => MeanMs.ToString("F2", CultureInfo.InvariantCulture).PadLeft(10); public string P95Column => P95Ms.ToString("F2", CultureInfo.InvariantCulture).PadLeft(10); public string MaxColumn => MaxMs.ToString("F2", CultureInfo.InvariantCulture).PadLeft(10); public string MinThroughputColumn => (MinThroughputPerSecond / 1_000d).ToString("F2", CultureInfo.InvariantCulture).PadLeft(11); public string AllocatedColumn => MaxAllocatedMb.ToString("F2", CultureInfo.InvariantCulture).PadLeft(9); } internal readonly record struct DurationStatistics(double MeanMs, double P95Ms, double MaxMs) { public static DurationStatistics From(IReadOnlyList durations) { if (durations.Count == 0) { return new DurationStatistics(0, 0, 0); } var sorted = durations.ToArray(); Array.Sort(sorted); var total = 0d; foreach (var value in durations) { total += value; } var mean = total / durations.Count; var p95 = Percentile(sorted, 95); var max = sorted[^1]; return new DurationStatistics(mean, p95, max); } private static double Percentile(IReadOnlyList sorted, double percentile) { if (sorted.Count == 0) { return 0; } var rank = (percentile / 100d) * (sorted.Count - 1); var lower = (int)Math.Floor(rank); var upper = (int)Math.Ceiling(rank); var weight = rank - lower; if (upper >= sorted.Count) { return sorted[lower]; } return sorted[lower] + weight * (sorted[upper] - sorted[lower]); } } internal readonly record struct ThroughputStatistics(double MeanPerSecond, double MinPerSecond) { public static ThroughputStatistics From(IReadOnlyList values) { if (values.Count == 0) { return new ThroughputStatistics(0, 0); } var total = 0d; var min = double.MaxValue; foreach (var value in values) { total += value; min = Math.Min(min, value); } var mean = total / values.Count; return new ThroughputStatistics(mean, min); } } internal readonly record struct AllocationStatistics(double MaxAllocatedMb) { public static AllocationStatistics From(IReadOnlyList values) { var max = 0d; foreach (var value in values) { max = Math.Max(max, value); } return new AllocationStatistics(max); } }