using System.Globalization; using System.Text; namespace StellaOps.Bench.ScannerAnalyzers.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 scanner_analyzer_bench_duration_ms Analyzer benchmark duration metrics in milliseconds."); builder.AppendLine("# TYPE scanner_analyzer_bench_duration_ms gauge"); foreach (var report in reports) { var scenarioLabel = Escape(report.Result.Id); AppendMetric(builder, "scanner_analyzer_bench_mean_ms", scenarioLabel, report.Result.MeanMs); AppendMetric(builder, "scanner_analyzer_bench_p95_ms", scenarioLabel, report.Result.P95Ms); AppendMetric(builder, "scanner_analyzer_bench_max_ms", scenarioLabel, report.Result.MaxMs); AppendMetric(builder, "scanner_analyzer_bench_threshold_ms", scenarioLabel, report.Result.ThresholdMs); if (report.Baseline is { } baseline) { AppendMetric(builder, "scanner_analyzer_bench_baseline_max_ms", scenarioLabel, baseline.MaxMs); AppendMetric(builder, "scanner_analyzer_bench_baseline_mean_ms", scenarioLabel, baseline.MeanMs); } if (report.MaxRegressionRatio is { } ratio) { AppendMetric(builder, "scanner_analyzer_bench_regression_ratio", scenarioLabel, ratio); AppendMetric(builder, "scanner_analyzer_bench_regression_limit", scenarioLabel, report.RegressionLimit); AppendMetric(builder, "scanner_analyzer_bench_regression_breached", scenarioLabel, report.RegressionBreached ? 1 : 0); } } File.WriteAllText(resolved, builder.ToString(), Encoding.UTF8); } private static void AppendMetric(StringBuilder builder, string metric, string scenarioLabel, double value) { builder.Append(metric); builder.Append("{scenario=\""); builder.Append(scenarioLabel); builder.Append("\"} "); builder.AppendLine(value.ToString("G17", CultureInfo.InvariantCulture)); } private static string Escape(string value) => value.Replace("\\", "\\\\", StringComparison.Ordinal).Replace("\"", "\\\"", StringComparison.Ordinal); }