Files
Oleksandr Bezdieniezhnykh 001e80fe96 [AZ-585] [AZ-586] ResLim+Perf NFT tests; close test cycle 1
Batch 4 of test implementation cycle 1 (existing-code Step 6, final batch).

- AZ-585 SteadyStateLoadTests + ColdStartRssTests: NFT-RES-LIM-01..04.
  SteadyStateLoadFixture runs one 5-min sustained-load window and samples
  RSS (docker stats), Npgsql conns (pg_stat_activity), and FDs
  (/proc/1/fd) every 5s; three test methods assert independently. All
  SkippableFact-gated on docker primitives.
- AZ-586 PerformanceTests: NFT-PERF-01..04. Sequential single-client,
  5 warm-ups + N measured calls, P50+P95 via LatencyPercentiles, recorded
  to PERF_RESULTS_FILE. Tagged Category=Perf so default gate excludes them.

Infrastructure:
- entrypoint.sh now applies --filter "${TEST_FILTER:-Category!=Perf}"
  per AZ-586 (default CI gate excludes performance).
- MetricCsvRecorder: idempotent CSV appender keyed on env var, used by
  both Perf and ResLim categories.

Step 6 (Implement Tests) is complete. Final report at
_docs/03_implementation/implementation_report_tests.md handoffs the
full-suite gate to test-run/SKILL.md (Step 7).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-15 09:11:53 +03:00

39 lines
1.6 KiB
C#

namespace Azaion.Missions.E2E.Helpers;
/// <summary>
/// Median + percentile helper for the NFT-PERF-* and NFT-RES-LIM-01
/// scenarios. Inputs are wall-clock latency samples (or RSS samples)
/// in any orderable numeric type; the helper sorts a defensive copy
/// and uses the "nearest-rank" definition of percentile (matching the
/// percentile defaults used in `docker stats` and most CI dashboards).
/// </summary>
public static class LatencyPercentiles
{
public static double P50(IReadOnlyList<double> samples) => Percentile(samples, 50);
public static double P95(IReadOnlyList<double> samples) => Percentile(samples, 95);
public static double Percentile(IReadOnlyList<double> samples, int percentile)
{
if (samples.Count == 0)
throw new ArgumentException("samples must contain at least one value", nameof(samples));
if (percentile < 0 || percentile > 100)
throw new ArgumentOutOfRangeException(nameof(percentile), "percentile must be in [0, 100]");
var sorted = samples.ToArray();
Array.Sort(sorted);
// Nearest-rank: rank = ceil(p/100 * N); index = rank - 1.
var rank = (int)Math.Ceiling(percentile / 100.0 * sorted.Length);
if (rank < 1) rank = 1;
if (rank > sorted.Length) rank = sorted.Length;
return sorted[rank - 1];
}
public static double Mean(IReadOnlyList<double> samples)
{
if (samples.Count == 0)
throw new ArgumentException("samples must contain at least one value", nameof(samples));
return samples.Average();
}
}