feat(rate-limiting): Implement core rate limiting functionality with configuration, decision-making, metrics, middleware, and service registration

- Add RateLimitConfig for configuration management with YAML binding support.
- Introduce RateLimitDecision to encapsulate the result of rate limit checks.
- Implement RateLimitMetrics for OpenTelemetry metrics tracking.
- Create RateLimitMiddleware for enforcing rate limits on incoming requests.
- Develop RateLimitService to orchestrate instance and environment rate limit checks.
- Add RateLimitServiceCollectionExtensions for dependency injection registration.
This commit is contained in:
master
2025-12-17 18:02:37 +02:00
parent 394b57f6bf
commit 8bbfe4d2d2
211 changed files with 47179 additions and 1590 deletions

View File

@@ -0,0 +1,430 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Sprint: SPRINT_3500_0001_0001
// Task: SDIFF-MASTER-0007 - Performance benchmark suite
using System.Diagnostics;
using System.Text.Json;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;
using FluentAssertions;
using Xunit;
namespace StellaOps.Scanner.SmartDiff.Tests.Benchmarks;
/// <summary>
/// BenchmarkDotNet performance benchmarks for Smart-Diff operations.
/// Run with: dotnet run -c Release --project StellaOps.Scanner.SmartDiff.Tests.csproj -- --filter *SmartDiff*
/// </summary>
[Config(typeof(SmartDiffBenchmarkConfig))]
[MemoryDiagnoser]
[RankColumn]
public class SmartDiffPerformanceBenchmarks
{
private ScanData _smallBaseline = null!;
private ScanData _smallCurrent = null!;
private ScanData _mediumBaseline = null!;
private ScanData _mediumCurrent = null!;
private ScanData _largeBaseline = null!;
private ScanData _largeCurrent = null!;
[GlobalSetup]
public void Setup()
{
// Small: 50 packages, 10 vulnerabilities
_smallBaseline = GenerateScanData(packageCount: 50, vulnCount: 10);
_smallCurrent = GenerateScanData(packageCount: 55, vulnCount: 12, deltaPercent: 0.2);
// Medium: 500 packages, 100 vulnerabilities
_mediumBaseline = GenerateScanData(packageCount: 500, vulnCount: 100);
_mediumCurrent = GenerateScanData(packageCount: 520, vulnCount: 110, deltaPercent: 0.15);
// Large: 5000 packages, 1000 vulnerabilities
_largeBaseline = GenerateScanData(packageCount: 5000, vulnCount: 1000);
_largeCurrent = GenerateScanData(packageCount: 5100, vulnCount: 1050, deltaPercent: 0.10);
}
[Benchmark(Baseline = true)]
public DiffResult SmallScan_ComputeDiff()
{
return ComputeDiff(_smallBaseline, _smallCurrent);
}
[Benchmark]
public DiffResult MediumScan_ComputeDiff()
{
return ComputeDiff(_mediumBaseline, _mediumCurrent);
}
[Benchmark]
public DiffResult LargeScan_ComputeDiff()
{
return ComputeDiff(_largeBaseline, _largeCurrent);
}
[Benchmark]
public string SmallScan_GenerateSarif()
{
var diff = ComputeDiff(_smallBaseline, _smallCurrent);
return GenerateSarif(diff);
}
[Benchmark]
public string MediumScan_GenerateSarif()
{
var diff = ComputeDiff(_mediumBaseline, _mediumCurrent);
return GenerateSarif(diff);
}
[Benchmark]
public string LargeScan_GenerateSarif()
{
var diff = ComputeDiff(_largeBaseline, _largeCurrent);
return GenerateSarif(diff);
}
#region Benchmark Helpers
private static ScanData GenerateScanData(int packageCount, int vulnCount, double deltaPercent = 0)
{
var random = new Random(42); // Fixed seed for reproducibility
var packages = new List<PackageInfo>();
var vulnerabilities = new List<VulnInfo>();
for (int i = 0; i < packageCount; i++)
{
packages.Add(new PackageInfo
{
Name = $"package-{i:D5}",
Version = $"1.{random.Next(0, 10)}.{random.Next(0, 100)}",
Ecosystem = random.Next(0, 3) switch { 0 => "npm", 1 => "nuget", _ => "pypi" }
});
}
for (int i = 0; i < vulnCount; i++)
{
var pkg = packages[random.Next(0, packages.Count)];
vulnerabilities.Add(new VulnInfo
{
CveId = $"CVE-2024-{10000 + i}",
Package = pkg.Name,
Version = pkg.Version,
Severity = random.Next(0, 4) switch { 0 => "LOW", 1 => "MEDIUM", 2 => "HIGH", _ => "CRITICAL" },
IsReachable = random.NextDouble() > 0.6,
ReachabilityTier = random.Next(0, 3) switch { 0 => "imported", 1 => "called", _ => "executed" }
});
}
// Apply delta for current scans
if (deltaPercent > 0)
{
int vulnsToAdd = (int)(vulnCount * deltaPercent);
for (int i = 0; i < vulnsToAdd; i++)
{
var pkg = packages[random.Next(0, packages.Count)];
vulnerabilities.Add(new VulnInfo
{
CveId = $"CVE-2024-{20000 + i}",
Package = pkg.Name,
Version = pkg.Version,
Severity = "HIGH",
IsReachable = true,
ReachabilityTier = "executed"
});
}
}
return new ScanData { Packages = packages, Vulnerabilities = vulnerabilities };
}
private static DiffResult ComputeDiff(ScanData baseline, ScanData current)
{
var baselineSet = baseline.Vulnerabilities.ToHashSet(new VulnComparer());
var currentSet = current.Vulnerabilities.ToHashSet(new VulnComparer());
var added = current.Vulnerabilities.Where(v => !baselineSet.Contains(v)).ToList();
var removed = baseline.Vulnerabilities.Where(v => !currentSet.Contains(v)).ToList();
// Detect reachability flips
var baselineDict = baseline.Vulnerabilities.ToDictionary(v => v.CveId);
var reachabilityFlips = new List<VulnInfo>();
foreach (var curr in current.Vulnerabilities)
{
if (baselineDict.TryGetValue(curr.CveId, out var prev) && prev.IsReachable != curr.IsReachable)
{
reachabilityFlips.Add(curr);
}
}
return new DiffResult
{
Added = added,
Removed = removed,
ReachabilityFlips = reachabilityFlips,
TotalBaselineVulns = baseline.Vulnerabilities.Count,
TotalCurrentVulns = current.Vulnerabilities.Count
};
}
private static string GenerateSarif(DiffResult diff)
{
var sarif = new
{
version = "2.1.0",
schema = "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
runs = new[]
{
new
{
tool = new
{
driver = new
{
name = "StellaOps Smart-Diff",
version = "1.0.0",
informationUri = "https://stellaops.io"
}
},
results = diff.Added.Select(v => new
{
ruleId = v.CveId,
level = v.Severity == "CRITICAL" || v.Severity == "HIGH" ? "error" : "warning",
message = new { text = $"New vulnerability {v.CveId} in {v.Package}@{v.Version}" },
locations = new[]
{
new
{
physicalLocation = new
{
artifactLocation = new { uri = $"pkg:{v.Package}@{v.Version}" }
}
}
}
}).ToArray()
}
}
};
return JsonSerializer.Serialize(sarif, new JsonSerializerOptions { WriteIndented = false });
}
#endregion
}
/// <summary>
/// Performance threshold tests that fail CI if benchmarks regress.
/// </summary>
public sealed class SmartDiffPerformanceTests
{
[Fact]
public void SmallScan_ShouldCompleteWithin50ms()
{
// Arrange
var baseline = GenerateTestData(50, 10);
var current = GenerateTestData(55, 12);
// Act
var sw = Stopwatch.StartNew();
var result = ComputeDiff(baseline, current);
sw.Stop();
// Assert
sw.ElapsedMilliseconds.Should().BeLessThan(50, "Small scan diff should complete within 50ms");
result.Should().NotBeNull();
}
[Fact]
public void MediumScan_ShouldCompleteWithin200ms()
{
// Arrange
var baseline = GenerateTestData(500, 100);
var current = GenerateTestData(520, 110);
// Act
var sw = Stopwatch.StartNew();
var result = ComputeDiff(baseline, current);
sw.Stop();
// Assert
sw.ElapsedMilliseconds.Should().BeLessThan(200, "Medium scan diff should complete within 200ms");
result.Should().NotBeNull();
}
[Fact]
public void LargeScan_ShouldCompleteWithin2000ms()
{
// Arrange
var baseline = GenerateTestData(5000, 1000);
var current = GenerateTestData(5100, 1050);
// Act
var sw = Stopwatch.StartNew();
var result = ComputeDiff(baseline, current);
sw.Stop();
// Assert
sw.ElapsedMilliseconds.Should().BeLessThan(2000, "Large scan diff should complete within 2 seconds");
result.Should().NotBeNull();
}
[Fact]
public void SarifGeneration_ShouldCompleteWithin100ms_ForSmallDiff()
{
// Arrange
var baseline = GenerateTestData(50, 10);
var current = GenerateTestData(55, 15);
var diff = ComputeDiff(baseline, current);
// Act
var sw = Stopwatch.StartNew();
var sarif = GenerateSarif(diff);
sw.Stop();
// Assert
sw.ElapsedMilliseconds.Should().BeLessThan(100, "SARIF generation should complete within 100ms");
sarif.Should().Contain("2.1.0");
}
[Fact]
public void MemoryUsage_ShouldBeReasonable_ForLargeScan()
{
// Arrange
var baseline = GenerateTestData(5000, 1000);
var current = GenerateTestData(5100, 1050);
var memBefore = GC.GetTotalMemory(forceFullCollection: true);
// Act
var result = ComputeDiff(baseline, current);
var sarif = GenerateSarif(result);
var memAfter = GC.GetTotalMemory(forceFullCollection: false);
var memUsedMB = (memAfter - memBefore) / (1024.0 * 1024.0);
// Assert
memUsedMB.Should().BeLessThan(100, "Large scan diff should use less than 100MB of memory");
}
#region Helpers
private static ScanData GenerateTestData(int packageCount, int vulnCount)
{
var random = new Random(42);
var packages = Enumerable.Range(0, packageCount)
.Select(i => new PackageInfo { Name = $"pkg-{i}", Version = "1.0.0", Ecosystem = "npm" })
.ToList();
var vulns = Enumerable.Range(0, vulnCount)
.Select(i => new VulnInfo
{
CveId = $"CVE-2024-{i}",
Package = packages[random.Next(packages.Count)].Name,
Version = "1.0.0",
Severity = "HIGH",
IsReachable = random.NextDouble() > 0.5,
ReachabilityTier = "executed"
})
.ToList();
return new ScanData { Packages = packages, Vulnerabilities = vulns };
}
private static DiffResult ComputeDiff(ScanData baseline, ScanData current)
{
var baselineSet = baseline.Vulnerabilities.Select(v => v.CveId).ToHashSet();
var currentSet = current.Vulnerabilities.Select(v => v.CveId).ToHashSet();
return new DiffResult
{
Added = current.Vulnerabilities.Where(v => !baselineSet.Contains(v.CveId)).ToList(),
Removed = baseline.Vulnerabilities.Where(v => !currentSet.Contains(v.CveId)).ToList(),
ReachabilityFlips = new List<VulnInfo>(),
TotalBaselineVulns = baseline.Vulnerabilities.Count,
TotalCurrentVulns = current.Vulnerabilities.Count
};
}
private static string GenerateSarif(DiffResult diff)
{
return JsonSerializer.Serialize(new
{
version = "2.1.0",
runs = new[] { new { results = diff.Added.Count } }
});
}
#endregion
}
#region Benchmark Config
public sealed class SmartDiffBenchmarkConfig : ManualConfig
{
public SmartDiffBenchmarkConfig()
{
AddJob(Job.ShortRun
.WithWarmupCount(3)
.WithIterationCount(5));
AddLogger(ConsoleLogger.Default);
AddExporter(MarkdownExporter.GitHub);
AddExporter(HtmlExporter.Default);
AddColumnProvider(DefaultColumnProviders.Instance);
}
}
#endregion
#region Models
public sealed class ScanData
{
public List<PackageInfo> Packages { get; set; } = new();
public List<VulnInfo> Vulnerabilities { get; set; } = new();
}
public sealed class PackageInfo
{
public string Name { get; set; } = "";
public string Version { get; set; } = "";
public string Ecosystem { get; set; } = "";
}
public sealed class VulnInfo
{
public string CveId { get; set; } = "";
public string Package { get; set; } = "";
public string Version { get; set; } = "";
public string Severity { get; set; } = "";
public bool IsReachable { get; set; }
public string ReachabilityTier { get; set; } = "";
}
public sealed class DiffResult
{
public List<VulnInfo> Added { get; set; } = new();
public List<VulnInfo> Removed { get; set; } = new();
public List<VulnInfo> ReachabilityFlips { get; set; } = new();
public int TotalBaselineVulns { get; set; }
public int TotalCurrentVulns { get; set; }
}
public sealed class VulnComparer : IEqualityComparer<VulnInfo>
{
public bool Equals(VulnInfo? x, VulnInfo? y)
{
if (x is null || y is null) return false;
return x.CveId == y.CveId && x.Package == y.Package && x.Version == y.Version;
}
public int GetHashCode(VulnInfo obj)
{
return HashCode.Combine(obj.CveId, obj.Package, obj.Version);
}
}
#endregion