using System.Diagnostics;
namespace ZB.MOM.WW.ScadaBridge.ResidualBench;
/// --key value options. An unknown key is rejected by the consuming mode, not silently ignored.
internal sealed class BenchArgs
{
private readonly Dictionary _values = new(StringComparer.Ordinal);
private readonly HashSet _read = new(StringComparer.Ordinal);
public static BenchArgs Parse(IEnumerable args)
{
var result = new BenchArgs();
var list = args.ToList();
for (var i = 0; i < list.Count; i++)
{
if (!list[i].StartsWith("--", StringComparison.Ordinal))
throw new ArgumentException($"unexpected argument '{list[i]}'");
var key = list[i][2..];
var value = i + 1 < list.Count && !list[i + 1].StartsWith("--", StringComparison.Ordinal) ? list[++i] : "true";
result._values[key] = value;
}
return result;
}
public string Get(string key, string fallback)
{
_read.Add(key);
return _values.TryGetValue(key, out var v) ? v : fallback;
}
public int GetInt(string key, int fallback) => int.Parse(Get(key, fallback.ToString()));
/// Throws if any supplied key was never read — a typo must not silently change the run.
public void RejectUnknown()
{
var unknown = _values.Keys.Where(k => !_read.Contains(k)).ToList();
if (unknown.Count > 0)
throw new ArgumentException($"unknown option(s): {string.Join(", ", unknown.Select(k => "--" + k))}");
}
}
/// Latency sample set with nearest-rank percentiles.
internal sealed class Samples
{
private readonly List _ms = new();
public int Count => _ms.Count;
public void Add(double ms) { lock (_ms) _ms.Add(ms); }
public double Percentile(double p)
{
lock (_ms)
{
if (_ms.Count == 0) return double.NaN;
var sorted = _ms.OrderBy(x => x).ToList();
var rank = (int)Math.Ceiling(p / 100.0 * sorted.Count) - 1;
return sorted[Math.Clamp(rank, 0, sorted.Count - 1)];
}
}
public double Max { get { lock (_ms) return _ms.Count == 0 ? double.NaN : _ms.Max(); } }
public string Summary() =>
$"n={Count} P50 {Percentile(50):F2} ms, P95 {Percentile(95):F2} ms, P99 {Percentile(99):F2} ms, max {Max:F2} ms";
public static double Time(Action action)
{
var sw = Stopwatch.StartNew();
action();
return sw.Elapsed.TotalMilliseconds;
}
}