Some checks failed
		
		
	
	Docs CI / lint-and-preview (push) Has been cancelled
				
			- Created SignerEndpointsTests to validate the SignDsse and VerifyReferrers endpoints. - Implemented StubBearerAuthenticationDefaults and StubBearerAuthenticationHandler for token-based authentication. - Developed ConcelierExporterClient for managing Trivy DB settings and export operations. - Added TrivyDbSettingsPageComponent for UI interactions with Trivy DB settings, including form handling and export triggering. - Implemented styles and HTML structure for Trivy DB settings page. - Created NotifySmokeCheck tool for validating Redis event streams and Notify deliveries.
		
			
				
	
	
		
			303 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			303 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System.Globalization;
 | |
| using StellaOps.Bench.ScannerAnalyzers.Scenarios;
 | |
| 
 | |
| namespace StellaOps.Bench.ScannerAnalyzers;
 | |
| 
 | |
| internal static class Program
 | |
| {
 | |
|     public static async Task<int> Main(string[] args)
 | |
|     {
 | |
|         try
 | |
|         {
 | |
|             var options = ProgramOptions.Parse(args);
 | |
|             var config = await BenchmarkConfig.LoadAsync(options.ConfigPath).ConfigureAwait(false);
 | |
| 
 | |
|             var iterations = options.Iterations ?? config.Iterations ?? 5;
 | |
|             var thresholdMs = options.ThresholdMs ?? config.ThresholdMs ?? 5000;
 | |
|             var repoRoot = ResolveRepoRoot(options.RepoRoot, options.ConfigPath);
 | |
| 
 | |
|             var results = new List<ScenarioResult>();
 | |
|             var failures = new List<string>();
 | |
| 
 | |
|             foreach (var scenario in config.Scenarios)
 | |
|             {
 | |
|                 var runner = ScenarioRunnerFactory.Create(scenario);
 | |
|                 var scenarioRoot = ResolveScenarioRoot(repoRoot, scenario.Root!);
 | |
| 
 | |
|                 var execution = await runner.ExecuteAsync(scenarioRoot, iterations, CancellationToken.None).ConfigureAwait(false);
 | |
|                 var stats = ScenarioStatistics.FromDurations(execution.Durations);
 | |
|                 var scenarioThreshold = scenario.ThresholdMs ?? thresholdMs;
 | |
| 
 | |
|                 results.Add(new ScenarioResult(
 | |
|                     scenario.Id!,
 | |
|                     scenario.Label ?? scenario.Id!,
 | |
|                     execution.SampleCount,
 | |
|                     stats.MeanMs,
 | |
|                     stats.P95Ms,
 | |
|                     stats.MaxMs,
 | |
|                     iterations));
 | |
| 
 | |
|                 if (stats.MaxMs > scenarioThreshold)
 | |
|                 {
 | |
|                     failures.Add($"{scenario.Id} exceeded threshold: {stats.MaxMs:F2} ms > {scenarioThreshold:F2} ms");
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             TablePrinter.Print(results);
 | |
| 
 | |
|             if (!string.IsNullOrWhiteSpace(options.OutPath))
 | |
|             {
 | |
|                 CsvWriter.Write(options.OutPath!, results);
 | |
|             }
 | |
| 
 | |
|             if (failures.Count > 0)
 | |
|             {
 | |
|                 Console.Error.WriteLine();
 | |
|                 Console.Error.WriteLine("Performance threshold exceeded:");
 | |
|                 foreach (var failure in failures)
 | |
|                 {
 | |
|                     Console.Error.WriteLine($" - {failure}");
 | |
|                 }
 | |
| 
 | |
|                 return 1;
 | |
|             }
 | |
| 
 | |
|             return 0;
 | |
|         }
 | |
|         catch (Exception ex)
 | |
|         {
 | |
|             Console.Error.WriteLine(ex.Message);
 | |
|             return 1;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private static string ResolveRepoRoot(string? overridePath, string configPath)
 | |
|     {
 | |
|         if (!string.IsNullOrWhiteSpace(overridePath))
 | |
|         {
 | |
|             return Path.GetFullPath(overridePath);
 | |
|         }
 | |
| 
 | |
|         var configDirectory = Path.GetDirectoryName(configPath);
 | |
|         if (string.IsNullOrWhiteSpace(configDirectory))
 | |
|         {
 | |
|             return Directory.GetCurrentDirectory();
 | |
|         }
 | |
| 
 | |
|         return Path.GetFullPath(Path.Combine(configDirectory, "..", ".."));
 | |
|     }
 | |
| 
 | |
|     private static string ResolveScenarioRoot(string repoRoot, string relativeRoot)
 | |
|     {
 | |
|         if (string.IsNullOrWhiteSpace(relativeRoot))
 | |
|         {
 | |
|             throw new InvalidOperationException("Scenario root is required.");
 | |
|         }
 | |
| 
 | |
|         var combined = Path.GetFullPath(Path.Combine(repoRoot, relativeRoot));
 | |
|         if (!PathUtilities.IsWithinRoot(repoRoot, combined))
 | |
|         {
 | |
|             throw new InvalidOperationException($"Scenario root '{relativeRoot}' escapes repository root '{repoRoot}'.");
 | |
|         }
 | |
| 
 | |
|         if (!Directory.Exists(combined))
 | |
|         {
 | |
|             throw new DirectoryNotFoundException($"Scenario root '{combined}' does not exist.");
 | |
|         }
 | |
| 
 | |
|         return combined;
 | |
|     }
 | |
| 
 | |
|     private sealed record ProgramOptions(string ConfigPath, int? Iterations, double? ThresholdMs, string? OutPath, string? RepoRoot)
 | |
|     {
 | |
|         public static ProgramOptions Parse(string[] args)
 | |
|         {
 | |
|             var configPath = DefaultConfigPath();
 | |
|             int? iterations = null;
 | |
|             double? thresholdMs = null;
 | |
|             string? outPath = null;
 | |
|             string? repoRoot = null;
 | |
| 
 | |
|             for (var index = 0; index < args.Length; index++)
 | |
|             {
 | |
|                 var current = args[index];
 | |
|                 switch (current)
 | |
|                 {
 | |
|                     case "--config":
 | |
|                         EnsureNext(args, index);
 | |
|                         configPath = Path.GetFullPath(args[++index]);
 | |
|                         break;
 | |
|                     case "--iterations":
 | |
|                         EnsureNext(args, index);
 | |
|                         iterations = int.Parse(args[++index], CultureInfo.InvariantCulture);
 | |
|                         break;
 | |
|                     case "--threshold-ms":
 | |
|                         EnsureNext(args, index);
 | |
|                         thresholdMs = double.Parse(args[++index], CultureInfo.InvariantCulture);
 | |
|                         break;
 | |
|                     case "--out":
 | |
|                         EnsureNext(args, index);
 | |
|                         outPath = args[++index];
 | |
|                         break;
 | |
|                     case "--repo-root":
 | |
|                     case "--samples":
 | |
|                         EnsureNext(args, index);
 | |
|                         repoRoot = args[++index];
 | |
|                         break;
 | |
|                     default:
 | |
|                         throw new ArgumentException($"Unknown argument: {current}", nameof(args));
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             return new ProgramOptions(configPath, iterations, thresholdMs, outPath, repoRoot);
 | |
|         }
 | |
| 
 | |
|         private static string DefaultConfigPath()
 | |
|         {
 | |
|             var binaryDir = AppContext.BaseDirectory;
 | |
|             var projectRoot = Path.GetFullPath(Path.Combine(binaryDir, "..", "..", ".."));
 | |
|             var configDirectory = Path.GetFullPath(Path.Combine(projectRoot, ".."));
 | |
|             return Path.Combine(configDirectory, "config.json");
 | |
|         }
 | |
| 
 | |
|         private static void EnsureNext(string[] args, int index)
 | |
|         {
 | |
|             if (index + 1 >= args.Length)
 | |
|             {
 | |
|                 throw new ArgumentException("Missing value for argument.", nameof(args));
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private sealed record ScenarioResult(
 | |
|         string Id,
 | |
|         string Label,
 | |
|         int SampleCount,
 | |
|         double MeanMs,
 | |
|         double P95Ms,
 | |
|         double MaxMs,
 | |
|         int Iterations);
 | |
| 
 | |
|     private sealed record ScenarioStatistics(double MeanMs, double P95Ms, double MaxMs)
 | |
|     {
 | |
|         public static ScenarioStatistics FromDurations(IReadOnlyList<double> durations)
 | |
|         {
 | |
|             if (durations.Count == 0)
 | |
|             {
 | |
|                 return new ScenarioStatistics(0, 0, 0);
 | |
|             }
 | |
| 
 | |
|             var sorted = durations.ToArray();
 | |
|             Array.Sort(sorted);
 | |
| 
 | |
|             var total = 0d;
 | |
|             foreach (var value in durations)
 | |
|             {
 | |
|                 total += value;
 | |
|             }
 | |
| 
 | |
|             var mean = total / durations.Count;
 | |
|             var p95 = Percentile(sorted, 95);
 | |
|             var max = sorted[^1];
 | |
| 
 | |
|             return new ScenarioStatistics(mean, p95, max);
 | |
|         }
 | |
| 
 | |
|         private static double Percentile(IReadOnlyList<double> sorted, double percentile)
 | |
|         {
 | |
|             if (sorted.Count == 0)
 | |
|             {
 | |
|                 return 0;
 | |
|             }
 | |
| 
 | |
|             var rank = (percentile / 100d) * (sorted.Count - 1);
 | |
|             var lower = (int)Math.Floor(rank);
 | |
|             var upper = (int)Math.Ceiling(rank);
 | |
|             var weight = rank - lower;
 | |
| 
 | |
|             if (upper >= sorted.Count)
 | |
|             {
 | |
|                 return sorted[lower];
 | |
|             }
 | |
| 
 | |
|             return sorted[lower] + weight * (sorted[upper] - sorted[lower]);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private static class TablePrinter
 | |
|     {
 | |
|         public static void Print(IEnumerable<ScenarioResult> results)
 | |
|         {
 | |
|             Console.WriteLine("Scenario                     | Count |   Mean(ms) |    P95(ms) |     Max(ms)");
 | |
|             Console.WriteLine("---------------------------- | ----- | --------- | --------- | ----------");
 | |
|             foreach (var row in results)
 | |
|             {
 | |
|                 Console.WriteLine(FormatRow(row));
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         private static string FormatRow(ScenarioResult row)
 | |
|         {
 | |
|             var idColumn = row.Id.Length <= 28
 | |
|                 ? row.Id.PadRight(28)
 | |
|                 : row.Id[..28];
 | |
| 
 | |
|             return string.Join(" | ", new[]
 | |
|             {
 | |
|                 idColumn,
 | |
|                 row.SampleCount.ToString(CultureInfo.InvariantCulture).PadLeft(5),
 | |
|                 row.MeanMs.ToString("F2", CultureInfo.InvariantCulture).PadLeft(9),
 | |
|                 row.P95Ms.ToString("F2", CultureInfo.InvariantCulture).PadLeft(9),
 | |
|                 row.MaxMs.ToString("F2", CultureInfo.InvariantCulture).PadLeft(10),
 | |
|             });
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private static class CsvWriter
 | |
|     {
 | |
|         public static void Write(string path, IEnumerable<ScenarioResult> results)
 | |
|         {
 | |
|             var resolvedPath = Path.GetFullPath(path);
 | |
|             var directory = Path.GetDirectoryName(resolvedPath);
 | |
|             if (!string.IsNullOrEmpty(directory))
 | |
|             {
 | |
|                 Directory.CreateDirectory(directory);
 | |
|             }
 | |
| 
 | |
|             using var stream = new FileStream(resolvedPath, FileMode.Create, FileAccess.Write, FileShare.None);
 | |
|             using var writer = new StreamWriter(stream);
 | |
|             writer.WriteLine("scenario,iterations,sample_count,mean_ms,p95_ms,max_ms");
 | |
| 
 | |
|             foreach (var row in results)
 | |
|             {
 | |
|                 writer.Write(row.Id);
 | |
|                 writer.Write(',');
 | |
|                 writer.Write(row.Iterations.ToString(CultureInfo.InvariantCulture));
 | |
|                 writer.Write(',');
 | |
|                 writer.Write(row.SampleCount.ToString(CultureInfo.InvariantCulture));
 | |
|                 writer.Write(',');
 | |
|                 writer.Write(row.MeanMs.ToString("F4", CultureInfo.InvariantCulture));
 | |
|                 writer.Write(',');
 | |
|                 writer.Write(row.P95Ms.ToString("F4", CultureInfo.InvariantCulture));
 | |
|                 writer.Write(',');
 | |
|                 writer.Write(row.MaxMs.ToString("F4", CultureInfo.InvariantCulture));
 | |
|                 writer.WriteLine();
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     internal static class PathUtilities
 | |
|     {
 | |
|         public static bool IsWithinRoot(string root, string candidate)
 | |
|         {
 | |
|             var relative = Path.GetRelativePath(root, candidate);
 | |
|             if (string.IsNullOrEmpty(relative) || relative == ".")
 | |
|             {
 | |
|                 return true;
 | |
|             }
 | |
| 
 | |
|             return !relative.StartsWith("..", StringComparison.Ordinal) && !Path.IsPathRooted(relative);
 | |
|         }
 | |
|     }
 | |
| }
 |