Add Policy DSL Validator, Schema Exporter, and Simulation Smoke tools
- Implemented PolicyDslValidator with command-line options for strict mode and JSON output. - Created PolicySchemaExporter to generate JSON schemas for policy-related models. - Developed PolicySimulationSmoke tool to validate policy simulations against expected outcomes. - Added project files and necessary dependencies for each tool. - Ensured proper error handling and usage instructions across tools.
This commit is contained in:
		@@ -0,0 +1,151 @@
 | 
			
		||||
using System.Text.Json;
 | 
			
		||||
using System.Text.Json.Serialization;
 | 
			
		||||
 | 
			
		||||
namespace StellaOps.Bench.LinkNotMerge.Vex.Reporting;
 | 
			
		||||
 | 
			
		||||
internal static class BenchmarkJsonWriter
 | 
			
		||||
{
 | 
			
		||||
    private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web)
 | 
			
		||||
    {
 | 
			
		||||
        WriteIndented = true,
 | 
			
		||||
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    public static async Task WriteAsync(
 | 
			
		||||
        string path,
 | 
			
		||||
        BenchmarkJsonMetadata metadata,
 | 
			
		||||
        IReadOnlyList<BenchmarkScenarioReport> reports,
 | 
			
		||||
        CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        ArgumentException.ThrowIfNullOrWhiteSpace(path);
 | 
			
		||||
        ArgumentNullException.ThrowIfNull(metadata);
 | 
			
		||||
        ArgumentNullException.ThrowIfNull(reports);
 | 
			
		||||
 | 
			
		||||
        var resolved = Path.GetFullPath(path);
 | 
			
		||||
        var directory = Path.GetDirectoryName(resolved);
 | 
			
		||||
        if (!string.IsNullOrEmpty(directory))
 | 
			
		||||
        {
 | 
			
		||||
            Directory.CreateDirectory(directory);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var document = new BenchmarkJsonDocument(
 | 
			
		||||
            metadata.SchemaVersion,
 | 
			
		||||
            metadata.CapturedAtUtc,
 | 
			
		||||
            metadata.Commit,
 | 
			
		||||
            metadata.Environment,
 | 
			
		||||
            reports.Select(CreateScenario).ToArray());
 | 
			
		||||
 | 
			
		||||
        await using var stream = new FileStream(resolved, FileMode.Create, FileAccess.Write, FileShare.None);
 | 
			
		||||
        await JsonSerializer.SerializeAsync(stream, document, SerializerOptions, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
        await stream.FlushAsync(cancellationToken).ConfigureAwait(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static BenchmarkJsonScenario CreateScenario(BenchmarkScenarioReport report)
 | 
			
		||||
    {
 | 
			
		||||
        var baseline = report.Baseline;
 | 
			
		||||
        return new BenchmarkJsonScenario(
 | 
			
		||||
            report.Result.Id,
 | 
			
		||||
            report.Result.Label,
 | 
			
		||||
            report.Result.Iterations,
 | 
			
		||||
            report.Result.ObservationCount,
 | 
			
		||||
            report.Result.StatementCount,
 | 
			
		||||
            report.Result.EventCount,
 | 
			
		||||
            report.Result.TotalStatistics.MeanMs,
 | 
			
		||||
            report.Result.TotalStatistics.P95Ms,
 | 
			
		||||
            report.Result.TotalStatistics.MaxMs,
 | 
			
		||||
            report.Result.InsertStatistics.MeanMs,
 | 
			
		||||
            report.Result.CorrelationStatistics.MeanMs,
 | 
			
		||||
            report.Result.ObservationThroughputStatistics.MeanPerSecond,
 | 
			
		||||
            report.Result.ObservationThroughputStatistics.MinPerSecond,
 | 
			
		||||
            report.Result.EventThroughputStatistics.MeanPerSecond,
 | 
			
		||||
            report.Result.EventThroughputStatistics.MinPerSecond,
 | 
			
		||||
            report.Result.AllocationStatistics.MaxAllocatedMb,
 | 
			
		||||
            report.Result.ThresholdMs,
 | 
			
		||||
            report.Result.MinObservationThroughputPerSecond,
 | 
			
		||||
            report.Result.MinEventThroughputPerSecond,
 | 
			
		||||
            report.Result.MaxAllocatedThresholdMb,
 | 
			
		||||
            baseline is null
 | 
			
		||||
                ? null
 | 
			
		||||
                : new BenchmarkJsonScenarioBaseline(
 | 
			
		||||
                    baseline.Iterations,
 | 
			
		||||
                    baseline.Observations,
 | 
			
		||||
                    baseline.Statements,
 | 
			
		||||
                    baseline.Events,
 | 
			
		||||
                    baseline.MeanTotalMs,
 | 
			
		||||
                    baseline.P95TotalMs,
 | 
			
		||||
                    baseline.MaxTotalMs,
 | 
			
		||||
                    baseline.MeanInsertMs,
 | 
			
		||||
                    baseline.MeanCorrelationMs,
 | 
			
		||||
                    baseline.MeanObservationThroughputPerSecond,
 | 
			
		||||
                    baseline.MinObservationThroughputPerSecond,
 | 
			
		||||
                    baseline.MeanEventThroughputPerSecond,
 | 
			
		||||
                    baseline.MinEventThroughputPerSecond,
 | 
			
		||||
                    baseline.MaxAllocatedMb),
 | 
			
		||||
            new BenchmarkJsonScenarioRegression(
 | 
			
		||||
                report.DurationRegressionRatio,
 | 
			
		||||
                report.ObservationThroughputRegressionRatio,
 | 
			
		||||
                report.EventThroughputRegressionRatio,
 | 
			
		||||
                report.RegressionLimit,
 | 
			
		||||
                report.RegressionBreached));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private sealed record BenchmarkJsonDocument(
 | 
			
		||||
        string SchemaVersion,
 | 
			
		||||
        DateTimeOffset CapturedAt,
 | 
			
		||||
        string? Commit,
 | 
			
		||||
        string? Environment,
 | 
			
		||||
        IReadOnlyList<BenchmarkJsonScenario> Scenarios);
 | 
			
		||||
 | 
			
		||||
    private sealed record BenchmarkJsonScenario(
 | 
			
		||||
        string Id,
 | 
			
		||||
        string Label,
 | 
			
		||||
        int Iterations,
 | 
			
		||||
        int Observations,
 | 
			
		||||
        int Statements,
 | 
			
		||||
        int Events,
 | 
			
		||||
        double MeanTotalMs,
 | 
			
		||||
        double P95TotalMs,
 | 
			
		||||
        double MaxTotalMs,
 | 
			
		||||
        double MeanInsertMs,
 | 
			
		||||
        double MeanCorrelationMs,
 | 
			
		||||
        double MeanObservationThroughputPerSecond,
 | 
			
		||||
        double MinObservationThroughputPerSecond,
 | 
			
		||||
        double MeanEventThroughputPerSecond,
 | 
			
		||||
        double MinEventThroughputPerSecond,
 | 
			
		||||
        double MaxAllocatedMb,
 | 
			
		||||
        double? ThresholdMs,
 | 
			
		||||
        double? MinObservationThroughputThresholdPerSecond,
 | 
			
		||||
        double? MinEventThroughputThresholdPerSecond,
 | 
			
		||||
        double? MaxAllocatedThresholdMb,
 | 
			
		||||
        BenchmarkJsonScenarioBaseline? Baseline,
 | 
			
		||||
        BenchmarkJsonScenarioRegression Regression);
 | 
			
		||||
 | 
			
		||||
    private sealed record BenchmarkJsonScenarioBaseline(
 | 
			
		||||
        int Iterations,
 | 
			
		||||
        int Observations,
 | 
			
		||||
        int Statements,
 | 
			
		||||
        int Events,
 | 
			
		||||
        double MeanTotalMs,
 | 
			
		||||
        double P95TotalMs,
 | 
			
		||||
        double MaxTotalMs,
 | 
			
		||||
        double MeanInsertMs,
 | 
			
		||||
        double MeanCorrelationMs,
 | 
			
		||||
        double MeanObservationThroughputPerSecond,
 | 
			
		||||
        double MinObservationThroughputPerSecond,
 | 
			
		||||
        double MeanEventThroughputPerSecond,
 | 
			
		||||
        double MinEventThroughputPerSecond,
 | 
			
		||||
        double MaxAllocatedMb);
 | 
			
		||||
 | 
			
		||||
    private sealed record BenchmarkJsonScenarioRegression(
 | 
			
		||||
        double? DurationRatio,
 | 
			
		||||
        double? ObservationThroughputRatio,
 | 
			
		||||
        double? EventThroughputRatio,
 | 
			
		||||
        double Limit,
 | 
			
		||||
        bool Breached);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal sealed record BenchmarkJsonMetadata(
 | 
			
		||||
    string SchemaVersion,
 | 
			
		||||
    DateTimeOffset CapturedAtUtc,
 | 
			
		||||
    string? Commit,
 | 
			
		||||
    string? Environment);
 | 
			
		||||
@@ -0,0 +1,89 @@
 | 
			
		||||
using StellaOps.Bench.LinkNotMerge.Vex.Baseline;
 | 
			
		||||
 | 
			
		||||
namespace StellaOps.Bench.LinkNotMerge.Vex.Reporting;
 | 
			
		||||
 | 
			
		||||
internal sealed class BenchmarkScenarioReport
 | 
			
		||||
{
 | 
			
		||||
    private const double DefaultRegressionLimit = 1.15d;
 | 
			
		||||
 | 
			
		||||
    public BenchmarkScenarioReport(VexScenarioResult result, BaselineEntry? baseline, double? regressionLimit = null)
 | 
			
		||||
    {
 | 
			
		||||
        Result = result ?? throw new ArgumentNullException(nameof(result));
 | 
			
		||||
        Baseline = baseline;
 | 
			
		||||
        RegressionLimit = regressionLimit is { } limit && limit > 0 ? limit : DefaultRegressionLimit;
 | 
			
		||||
        DurationRegressionRatio = CalculateRatio(result.TotalStatistics.MaxMs, baseline?.MaxTotalMs);
 | 
			
		||||
        ObservationThroughputRegressionRatio = CalculateInverseRatio(result.ObservationThroughputStatistics.MinPerSecond, baseline?.MinObservationThroughputPerSecond);
 | 
			
		||||
        EventThroughputRegressionRatio = CalculateInverseRatio(result.EventThroughputStatistics.MinPerSecond, baseline?.MinEventThroughputPerSecond);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public VexScenarioResult Result { get; }
 | 
			
		||||
 | 
			
		||||
    public BaselineEntry? Baseline { get; }
 | 
			
		||||
 | 
			
		||||
    public double RegressionLimit { get; }
 | 
			
		||||
 | 
			
		||||
    public double? DurationRegressionRatio { get; }
 | 
			
		||||
 | 
			
		||||
    public double? ObservationThroughputRegressionRatio { get; }
 | 
			
		||||
 | 
			
		||||
    public double? EventThroughputRegressionRatio { get; }
 | 
			
		||||
 | 
			
		||||
    public bool DurationRegressionBreached => DurationRegressionRatio is { } ratio && ratio >= RegressionLimit;
 | 
			
		||||
 | 
			
		||||
    public bool ObservationThroughputRegressionBreached => ObservationThroughputRegressionRatio is { } ratio && ratio >= RegressionLimit;
 | 
			
		||||
 | 
			
		||||
    public bool EventThroughputRegressionBreached => EventThroughputRegressionRatio is { } ratio && ratio >= RegressionLimit;
 | 
			
		||||
 | 
			
		||||
    public bool RegressionBreached => DurationRegressionBreached || ObservationThroughputRegressionBreached || EventThroughputRegressionBreached;
 | 
			
		||||
 | 
			
		||||
    public IEnumerable<string> BuildRegressionFailureMessages()
 | 
			
		||||
    {
 | 
			
		||||
        if (Baseline is null)
 | 
			
		||||
        {
 | 
			
		||||
            yield break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (DurationRegressionBreached && DurationRegressionRatio is { } durationRatio)
 | 
			
		||||
        {
 | 
			
		||||
            var delta = (durationRatio - 1d) * 100d;
 | 
			
		||||
            yield return $"{Result.Id} exceeded max duration budget: {Result.TotalStatistics.MaxMs:F2} ms vs baseline {Baseline.MaxTotalMs:F2} ms (+{delta:F1}%).";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (ObservationThroughputRegressionBreached && ObservationThroughputRegressionRatio is { } obsRatio)
 | 
			
		||||
        {
 | 
			
		||||
            var delta = (obsRatio - 1d) * 100d;
 | 
			
		||||
            yield return $"{Result.Id} observation throughput regressed: min {Result.ObservationThroughputStatistics.MinPerSecond:N0} obs/s vs baseline {Baseline.MinObservationThroughputPerSecond:N0} obs/s (-{delta:F1}%).";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (EventThroughputRegressionBreached && EventThroughputRegressionRatio is { } evtRatio)
 | 
			
		||||
        {
 | 
			
		||||
            var delta = (evtRatio - 1d) * 100d;
 | 
			
		||||
            yield return $"{Result.Id} event throughput regressed: min {Result.EventThroughputStatistics.MinPerSecond:N0} events/s vs baseline {Baseline.MinEventThroughputPerSecond:N0} events/s (-{delta:F1}%).";
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static double? CalculateRatio(double current, double? baseline)
 | 
			
		||||
    {
 | 
			
		||||
        if (!baseline.HasValue || baseline.Value <= 0d)
 | 
			
		||||
        {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return current / baseline.Value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static double? CalculateInverseRatio(double current, double? baseline)
 | 
			
		||||
    {
 | 
			
		||||
        if (!baseline.HasValue || baseline.Value <= 0d)
 | 
			
		||||
        {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (current <= 0d)
 | 
			
		||||
        {
 | 
			
		||||
            return double.PositiveInfinity;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return baseline.Value / current;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,94 @@
 | 
			
		||||
using System.Globalization;
 | 
			
		||||
using System.Text;
 | 
			
		||||
 | 
			
		||||
namespace StellaOps.Bench.LinkNotMerge.Vex.Reporting;
 | 
			
		||||
 | 
			
		||||
internal static class PrometheusWriter
 | 
			
		||||
{
 | 
			
		||||
    public static void Write(string path, IReadOnlyList<BenchmarkScenarioReport> reports)
 | 
			
		||||
    {
 | 
			
		||||
        ArgumentException.ThrowIfNullOrWhiteSpace(path);
 | 
			
		||||
        ArgumentNullException.ThrowIfNull(reports);
 | 
			
		||||
 | 
			
		||||
        var resolved = Path.GetFullPath(path);
 | 
			
		||||
        var directory = Path.GetDirectoryName(resolved);
 | 
			
		||||
        if (!string.IsNullOrEmpty(directory))
 | 
			
		||||
        {
 | 
			
		||||
            Directory.CreateDirectory(directory);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var builder = new StringBuilder();
 | 
			
		||||
        builder.AppendLine("# HELP linknotmerge_vex_bench_total_ms Link-Not-Merge VEX benchmark total duration (milliseconds).");
 | 
			
		||||
        builder.AppendLine("# TYPE linknotmerge_vex_bench_total_ms gauge");
 | 
			
		||||
        builder.AppendLine("# HELP linknotmerge_vex_bench_throughput_per_sec Link-Not-Merge VEX benchmark observation throughput (observations per second).");
 | 
			
		||||
        builder.AppendLine("# TYPE linknotmerge_vex_bench_throughput_per_sec gauge");
 | 
			
		||||
        builder.AppendLine("# HELP linknotmerge_vex_bench_event_throughput_per_sec Link-Not-Merge VEX benchmark event throughput (events per second).");
 | 
			
		||||
        builder.AppendLine("# TYPE linknotmerge_vex_bench_event_throughput_per_sec gauge");
 | 
			
		||||
        builder.AppendLine("# HELP linknotmerge_vex_bench_allocated_mb Link-Not-Merge VEX benchmark max allocations (megabytes).");
 | 
			
		||||
        builder.AppendLine("# TYPE linknotmerge_vex_bench_allocated_mb gauge");
 | 
			
		||||
 | 
			
		||||
        foreach (var report in reports)
 | 
			
		||||
        {
 | 
			
		||||
            var scenario = Escape(report.Result.Id);
 | 
			
		||||
            AppendMetric(builder, "linknotmerge_vex_bench_mean_total_ms", scenario, report.Result.TotalStatistics.MeanMs);
 | 
			
		||||
            AppendMetric(builder, "linknotmerge_vex_bench_p95_total_ms", scenario, report.Result.TotalStatistics.P95Ms);
 | 
			
		||||
            AppendMetric(builder, "linknotmerge_vex_bench_max_total_ms", scenario, report.Result.TotalStatistics.MaxMs);
 | 
			
		||||
            AppendMetric(builder, "linknotmerge_vex_bench_threshold_ms", scenario, report.Result.ThresholdMs);
 | 
			
		||||
 | 
			
		||||
            AppendMetric(builder, "linknotmerge_vex_bench_mean_observation_throughput_per_sec", scenario, report.Result.ObservationThroughputStatistics.MeanPerSecond);
 | 
			
		||||
            AppendMetric(builder, "linknotmerge_vex_bench_min_observation_throughput_per_sec", scenario, report.Result.ObservationThroughputStatistics.MinPerSecond);
 | 
			
		||||
            AppendMetric(builder, "linknotmerge_vex_bench_observation_throughput_floor_per_sec", scenario, report.Result.MinObservationThroughputPerSecond);
 | 
			
		||||
 | 
			
		||||
            AppendMetric(builder, "linknotmerge_vex_bench_mean_event_throughput_per_sec", scenario, report.Result.EventThroughputStatistics.MeanPerSecond);
 | 
			
		||||
            AppendMetric(builder, "linknotmerge_vex_bench_min_event_throughput_per_sec", scenario, report.Result.EventThroughputStatistics.MinPerSecond);
 | 
			
		||||
            AppendMetric(builder, "linknotmerge_vex_bench_event_throughput_floor_per_sec", scenario, report.Result.MinEventThroughputPerSecond);
 | 
			
		||||
 | 
			
		||||
            AppendMetric(builder, "linknotmerge_vex_bench_max_allocated_mb", scenario, report.Result.AllocationStatistics.MaxAllocatedMb);
 | 
			
		||||
            AppendMetric(builder, "linknotmerge_vex_bench_max_allocated_threshold_mb", scenario, report.Result.MaxAllocatedThresholdMb);
 | 
			
		||||
 | 
			
		||||
            if (report.Baseline is { } baseline)
 | 
			
		||||
            {
 | 
			
		||||
                AppendMetric(builder, "linknotmerge_vex_bench_baseline_max_total_ms", scenario, baseline.MaxTotalMs);
 | 
			
		||||
                AppendMetric(builder, "linknotmerge_vex_bench_baseline_min_observation_throughput_per_sec", scenario, baseline.MinObservationThroughputPerSecond);
 | 
			
		||||
                AppendMetric(builder, "linknotmerge_vex_bench_baseline_min_event_throughput_per_sec", scenario, baseline.MinEventThroughputPerSecond);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (report.DurationRegressionRatio is { } durationRatio)
 | 
			
		||||
            {
 | 
			
		||||
                AppendMetric(builder, "linknotmerge_vex_bench_duration_regression_ratio", scenario, durationRatio);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (report.ObservationThroughputRegressionRatio is { } obsRatio)
 | 
			
		||||
            {
 | 
			
		||||
                AppendMetric(builder, "linknotmerge_vex_bench_observation_regression_ratio", scenario, obsRatio);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (report.EventThroughputRegressionRatio is { } evtRatio)
 | 
			
		||||
            {
 | 
			
		||||
                AppendMetric(builder, "linknotmerge_vex_bench_event_regression_ratio", scenario, evtRatio);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            AppendMetric(builder, "linknotmerge_vex_bench_regression_limit", scenario, report.RegressionLimit);
 | 
			
		||||
            AppendMetric(builder, "linknotmerge_vex_bench_regression_breached", scenario, report.RegressionBreached ? 1 : 0);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        File.WriteAllText(resolved, builder.ToString(), Encoding.UTF8);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void AppendMetric(StringBuilder builder, string metric, string scenario, double? value)
 | 
			
		||||
    {
 | 
			
		||||
        if (!value.HasValue)
 | 
			
		||||
        {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        builder.Append(metric);
 | 
			
		||||
        builder.Append("{scenario=\"");
 | 
			
		||||
        builder.Append(scenario);
 | 
			
		||||
        builder.Append("\"} ");
 | 
			
		||||
        builder.AppendLine(value.Value.ToString("G17", CultureInfo.InvariantCulture));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static string Escape(string value) =>
 | 
			
		||||
        value.Replace("\\", "\\\\", StringComparison.Ordinal).Replace("\"", "\\\"", StringComparison.Ordinal);
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user