5100* tests strengthtenen work
This commit is contained in:
169
tests/parity/StellaOps.Parity.Tests/LatencyComparisonLogic.cs
Normal file
169
tests/parity/StellaOps.Parity.Tests/LatencyComparisonLogic.cs
Normal file
@@ -0,0 +1,169 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// LatencyComparisonLogic.cs
|
||||
// Sprint: SPRINT_5100_0008_0001_competitor_parity
|
||||
// Task: PARITY-5100-006 - Implement latency comparison
|
||||
// Description: Logic for comparing scan latency between scanners
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
namespace StellaOps.Parity.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Compares latency metrics between scanner runs.
|
||||
/// </summary>
|
||||
public sealed class LatencyComparisonLogic
|
||||
{
|
||||
/// <summary>
|
||||
/// Compares latency from multiple scan runs.
|
||||
/// </summary>
|
||||
public LatencyComparisonResult Compare(
|
||||
IEnumerable<ScannerOutput> baselineRuns,
|
||||
IEnumerable<ScannerOutput> candidateRuns)
|
||||
{
|
||||
var baselineList = baselineRuns.Where(r => r.Success).ToList();
|
||||
var candidateList = candidateRuns.Where(r => r.Success).ToList();
|
||||
|
||||
if (baselineList.Count == 0 || candidateList.Count == 0)
|
||||
{
|
||||
return new LatencyComparisonResult
|
||||
{
|
||||
BaselineTool = baselineList.FirstOrDefault()?.ToolName ?? "unknown",
|
||||
CandidateTool = candidateList.FirstOrDefault()?.ToolName ?? "unknown",
|
||||
Error = "Insufficient successful runs for comparison"
|
||||
};
|
||||
}
|
||||
|
||||
var baselineMs = baselineList.Select(r => r.DurationMs).OrderBy(d => d).ToList();
|
||||
var candidateMs = candidateList.Select(r => r.DurationMs).OrderBy(d => d).ToList();
|
||||
|
||||
return new LatencyComparisonResult
|
||||
{
|
||||
BaselineTool = baselineList[0].ToolName,
|
||||
CandidateTool = candidateList[0].ToolName,
|
||||
Success = true,
|
||||
|
||||
// Baseline stats
|
||||
BaselineP50 = CalculatePercentile(baselineMs, 50),
|
||||
BaselineP95 = CalculatePercentile(baselineMs, 95),
|
||||
BaselineP99 = CalculatePercentile(baselineMs, 99),
|
||||
BaselineMin = baselineMs.Min(),
|
||||
BaselineMax = baselineMs.Max(),
|
||||
BaselineMean = baselineMs.Average(),
|
||||
BaselineStdDev = CalculateStdDev(baselineMs),
|
||||
BaselineSampleCount = baselineMs.Count,
|
||||
|
||||
// Candidate stats
|
||||
CandidateP50 = CalculatePercentile(candidateMs, 50),
|
||||
CandidateP95 = CalculatePercentile(candidateMs, 95),
|
||||
CandidateP99 = CalculatePercentile(candidateMs, 99),
|
||||
CandidateMin = candidateMs.Min(),
|
||||
CandidateMax = candidateMs.Max(),
|
||||
CandidateMean = candidateMs.Average(),
|
||||
CandidateStdDev = CalculateStdDev(candidateMs),
|
||||
CandidateSampleCount = candidateMs.Count,
|
||||
|
||||
// Comparison metrics
|
||||
P50Ratio = CalculatePercentile(candidateMs, 50) / Math.Max(1, CalculatePercentile(baselineMs, 50)),
|
||||
P95Ratio = CalculatePercentile(candidateMs, 95) / Math.Max(1, CalculatePercentile(baselineMs, 95)),
|
||||
MeanRatio = candidateMs.Average() / Math.Max(1, baselineMs.Average())
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates time-to-first-signal (TTFS) if available in scan output.
|
||||
/// </summary>
|
||||
public TimeToFirstSignalResult CalculateTtfs(ScannerOutput output)
|
||||
{
|
||||
return new TimeToFirstSignalResult
|
||||
{
|
||||
ToolName = output.ToolName,
|
||||
TotalDurationMs = output.DurationMs,
|
||||
// TTFS would require streaming output parsing, which most tools don't support
|
||||
// For now, we approximate as total duration
|
||||
TtfsMs = output.DurationMs,
|
||||
TtfsAvailable = false
|
||||
};
|
||||
}
|
||||
|
||||
private static double CalculatePercentile(List<long> sortedValues, int percentile)
|
||||
{
|
||||
if (sortedValues.Count == 0)
|
||||
return 0;
|
||||
|
||||
var index = (percentile / 100.0) * (sortedValues.Count - 1);
|
||||
var lower = (int)Math.Floor(index);
|
||||
var upper = (int)Math.Ceiling(index);
|
||||
|
||||
if (lower == upper)
|
||||
return sortedValues[lower];
|
||||
|
||||
var fraction = index - lower;
|
||||
return sortedValues[lower] * (1 - fraction) + sortedValues[upper] * fraction;
|
||||
}
|
||||
|
||||
private static double CalculateStdDev(List<long> values)
|
||||
{
|
||||
if (values.Count < 2)
|
||||
return 0;
|
||||
|
||||
var mean = values.Average();
|
||||
var sumSquares = values.Sum(v => Math.Pow(v - mean, 2));
|
||||
return Math.Sqrt(sumSquares / (values.Count - 1));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of latency comparison between two scanners.
|
||||
/// </summary>
|
||||
public sealed class LatencyComparisonResult
|
||||
{
|
||||
public required string BaselineTool { get; init; }
|
||||
public required string CandidateTool { get; init; }
|
||||
public bool Success { get; set; }
|
||||
public string? Error { get; set; }
|
||||
|
||||
// Baseline latency stats (milliseconds)
|
||||
public double BaselineP50 { get; set; }
|
||||
public double BaselineP95 { get; set; }
|
||||
public double BaselineP99 { get; set; }
|
||||
public long BaselineMin { get; set; }
|
||||
public long BaselineMax { get; set; }
|
||||
public double BaselineMean { get; set; }
|
||||
public double BaselineStdDev { get; set; }
|
||||
public int BaselineSampleCount { get; set; }
|
||||
|
||||
// Candidate latency stats (milliseconds)
|
||||
public double CandidateP50 { get; set; }
|
||||
public double CandidateP95 { get; set; }
|
||||
public double CandidateP99 { get; set; }
|
||||
public long CandidateMin { get; set; }
|
||||
public long CandidateMax { get; set; }
|
||||
public double CandidateMean { get; set; }
|
||||
public double CandidateStdDev { get; set; }
|
||||
public int CandidateSampleCount { get; set; }
|
||||
|
||||
// Comparison ratios (candidate / baseline, <1 means candidate is faster)
|
||||
public double P50Ratio { get; set; }
|
||||
public double P95Ratio { get; set; }
|
||||
public double MeanRatio { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if candidate is faster at P95 (ratio < 1).
|
||||
/// </summary>
|
||||
public bool CandidateIsFaster => P95Ratio < 1.0;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the percentage improvement (positive = candidate faster).
|
||||
/// </summary>
|
||||
public double ImprovementPercent => (1 - P95Ratio) * 100;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Time-to-first-signal measurement result.
|
||||
/// </summary>
|
||||
public sealed class TimeToFirstSignalResult
|
||||
{
|
||||
public required string ToolName { get; init; }
|
||||
public long TotalDurationMs { get; set; }
|
||||
public long TtfsMs { get; set; }
|
||||
public bool TtfsAvailable { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user