using System.Globalization; using System.Text; namespace StellaOps.Bench.LinkNotMerge.Vex.Reporting; internal static class PrometheusWriter { public static void Write(string path, IReadOnlyList 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); }