using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Text; namespace StellaOps.Bench.Notify.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 notify_dispatch_bench_duration_ms Notify dispatch benchmark duration metrics (milliseconds)."); builder.AppendLine("# TYPE notify_dispatch_bench_duration_ms gauge"); builder.AppendLine("# HELP notify_dispatch_bench_throughput_per_sec Notify dispatch benchmark throughput metrics (deliveries per second)."); builder.AppendLine("# TYPE notify_dispatch_bench_throughput_per_sec gauge"); builder.AppendLine("# HELP notify_dispatch_bench_allocation_mb Notify dispatch benchmark allocation metrics (megabytes)."); builder.AppendLine("# TYPE notify_dispatch_bench_allocation_mb gauge"); foreach (var report in reports) { var scenarioLabel = Escape(report.Result.Id); AppendMetric(builder, "notify_dispatch_bench_mean_ms", scenarioLabel, report.Result.MeanMs); AppendMetric(builder, "notify_dispatch_bench_p95_ms", scenarioLabel, report.Result.P95Ms); AppendMetric(builder, "notify_dispatch_bench_max_ms", scenarioLabel, report.Result.MaxMs); AppendMetric(builder, "notify_dispatch_bench_threshold_ms", scenarioLabel, report.Result.ThresholdMs); AppendMetric(builder, "notify_dispatch_bench_mean_throughput_per_sec", scenarioLabel, report.Result.MeanThroughputPerSecond); AppendMetric(builder, "notify_dispatch_bench_min_throughput_per_sec", scenarioLabel, report.Result.MinThroughputPerSecond); AppendMetric(builder, "notify_dispatch_bench_min_throughput_threshold_per_sec", scenarioLabel, report.Result.MinThroughputThresholdPerSecond); AppendMetric(builder, "notify_dispatch_bench_max_allocated_mb", scenarioLabel, report.Result.MaxAllocatedMb); AppendMetric(builder, "notify_dispatch_bench_max_allocated_threshold_mb", scenarioLabel, report.Result.MaxAllocatedThresholdMb); if (report.Baseline is { } baseline) { AppendMetric(builder, "notify_dispatch_bench_baseline_max_ms", scenarioLabel, baseline.MaxMs); AppendMetric(builder, "notify_dispatch_bench_baseline_mean_ms", scenarioLabel, baseline.MeanMs); AppendMetric(builder, "notify_dispatch_bench_baseline_min_throughput_per_sec", scenarioLabel, baseline.MinThroughputPerSecond); } if (report.DurationRegressionRatio is { } durationRatio) { AppendMetric(builder, "notify_dispatch_bench_duration_regression_ratio", scenarioLabel, durationRatio); } if (report.ThroughputRegressionRatio is { } throughputRatio) { AppendMetric(builder, "notify_dispatch_bench_throughput_regression_ratio", scenarioLabel, throughputRatio); } AppendMetric(builder, "notify_dispatch_bench_regression_limit", scenarioLabel, report.RegressionLimit); AppendMetric(builder, "notify_dispatch_bench_regression_breached", scenarioLabel, 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); }