using System.Diagnostics; using System.Text; using System.Text.Json; using RoslynStone.LoadTests.Serialization; namespace RoslynStone.LoadTests; public class Program { private const int DefaultConcurrency = 300; private const int DefaultRounds = 10; private const string DefaultBaseUrl = "http://localhost:7071"; public static async Task Main(string[] args) { // Parse command line arguments var baseUrl = args.Length > 0 ? args[0] : DefaultBaseUrl; var concurrency = args.Length > 1 ? int.Parse(args[1]) : DefaultConcurrency; var rounds = args.Length > 2 ? int.Parse(args[2]) : DefaultRounds; Console.WriteLine("╔═══════════════════════════════════════════════════════╗"); Console.WriteLine("║ Roslyn-Stone MCP Server Load Test ║"); Console.WriteLine("╚═══════════════════════════════════════════════════════╝"); Console.WriteLine(); Console.WriteLine($"Configuration:"); Console.WriteLine($" Base URL: {baseUrl}"); Console.WriteLine($" Concurrency: {concurrency}"); Console.WriteLine($" Rounds: {rounds}"); Console.WriteLine(); // Check if server is available Console.WriteLine("Checking server availability..."); // ReSharper disable once UseObjectOrCollectionInitializer - HttpClient initialization is safe using var client = new HttpClient { BaseAddress = new Uri(baseUrl), Timeout = TimeSpan.FromSeconds(30), }; try { var healthCheck = await client.GetAsync("/health"); if (!healthCheck.IsSuccessStatusCode) { Console.WriteLine($"⚠️ Server health check returned {healthCheck.StatusCode}"); } else { Console.WriteLine("✅ Server is available"); } } catch (Exception ex) { Console.WriteLine($"❌ Server is not available: {ex.Message}"); Console.WriteLine("\nPlease start the server with:"); Console.WriteLine(" cd src/RoslynStone.Api"); Console.WriteLine(" MCP_TRANSPORT=http dotnet run"); return; } Console.WriteLine(); Console.WriteLine("Starting load test..."); Console.WriteLine(); var loadTester = new LoadTester(client, concurrency, rounds); var results = await loadTester.RunAsync(); Console.WriteLine(); Console.WriteLine("╔═══════════════════════════════════════════════════════╗"); Console.WriteLine("║ Test Results ║"); Console.WriteLine("╚═══════════════════════════════════════════════════════╝"); Console.WriteLine(); results.PrintSummary(); } } public class LoadTester { private readonly HttpClient _client; private readonly int _concurrency; private readonly int _rounds; public LoadTester(HttpClient client, int concurrency, int rounds) { _client = client; _concurrency = concurrency; _rounds = rounds; } public async Task RunAsync() { var results = new LoadTestResults(_concurrency, _rounds); // Test scenarios var scenarios = new[] { ("Simple Expression", CreateSimpleExpressionRequest()), ("Variable Assignment", CreateVariableAssignmentRequest()), ("LINQ Query", CreateLinqQueryRequest()), ("Context Management", CreateContextManagementRequest()), }; foreach (var (scenarioName, requestContent) in scenarios) { Console.WriteLine($"Testing scenario: {scenarioName}"); var scenarioResults = await RunScenarioAsync(requestContent); results.AddScenario(scenarioName, scenarioResults); Console.WriteLine($" ✅ Completed"); } return results; } private async Task RunScenarioAsync(string requestContent) { var scenarioResults = new ScenarioResults(_rounds); for (int round = 0; round < _rounds; round++) { var sw = Stopwatch.StartNew(); var tasks = new List>(); for (int i = 0; i < _concurrency; i++) { tasks.Add(ExecuteRequestAsync(requestContent)); } var roundResults = await Task.WhenAll(tasks); sw.Stop(); var successCount = roundResults.Count(r => r.Success); var failureCount = roundResults.Count(r => !r.Success); var avgResponseTime = roundResults .Where(r => r.Success) .Select(r => r.ResponseTimeMs) .DefaultIfEmpty(0) .Average(); scenarioResults.AddRound( round, sw.ElapsedMilliseconds, successCount, failureCount, avgResponseTime ); } return scenarioResults; } private async Task ExecuteRequestAsync(string requestContent) { var sw = Stopwatch.StartNew(); try { using var content = new StringContent( requestContent, Encoding.UTF8, "application/json" ); var response = await _client.PostAsync("/mcp", content); sw.Stop(); return new RoundResult { Success = response.IsSuccessStatusCode, ResponseTimeMs = sw.ElapsedMilliseconds, StatusCode = (int)response.StatusCode, }; } catch (Exception ex) { sw.Stop(); return new RoundResult { Success = false, ResponseTimeMs = sw.ElapsedMilliseconds, Error = ex.Message, }; } } private static string CreateSimpleExpressionRequest() { var request = new { jsonrpc = "2.0", method = "tools/call", @params = new { name = "EvaluateCsharp", arguments = new { code = "2 + 2" } }, id = 1, }; return JsonSerializer.Serialize(request, LoadTestJsonContext.Default.Object); } private static string CreateVariableAssignmentRequest() { var request = new { jsonrpc = "2.0", method = "tools/call", @params = new { name = "EvaluateCsharp", arguments = new { code = "var x = 10; x * 2" }, }, id = 1, }; return JsonSerializer.Serialize(request, LoadTestJsonContext.Default.Object); } private static string CreateLinqQueryRequest() { var request = new { jsonrpc = "2.0", method = "tools/call", @params = new { name = "EvaluateCsharp", arguments = new { code = "Enumerable.Range(1, 100).Where(x => x % 2 == 0).Sum()" }, }, id = 1, }; return JsonSerializer.Serialize(request, LoadTestJsonContext.Default.Object); } private static string CreateContextManagementRequest() { var request = new { jsonrpc = "2.0", method = "tools/call", @params = new { name = "EvaluateCsharp", arguments = new { code = "int counter = 0; counter++; counter" }, }, id = 1, }; return JsonSerializer.Serialize(request, LoadTestJsonContext.Default.Object); } } public record RoundResult { public bool Success { get; init; } public long ResponseTimeMs { get; init; } // ReSharper disable once UnusedAutoPropertyAccessor.Global - Part of result contract public int StatusCode { get; init; } // ReSharper disable once UnusedAutoPropertyAccessor.Global - Part of result contract public string? Error { get; init; } } public class ScenarioResults { private readonly List<( int Round, long TotalMs, int Success, int Failures, double AvgResponseMs )> _rounds = new(); // ReSharper disable once NotAccessedField.Local - Reserved for future validation logic private readonly int _expectedRounds; public ScenarioResults(int expectedRounds) { _expectedRounds = expectedRounds; } public void AddRound( int round, long totalMs, int successCount, int failureCount, double avgResponseMs ) { _rounds.Add((round, totalMs, successCount, failureCount, avgResponseMs)); } public double AverageTotalTimeMs => _rounds.Average(r => r.TotalMs); public double AverageResponseTimeMs => _rounds.Average(r => r.AvgResponseMs); public int TotalSuccess => _rounds.Sum(r => r.Success); public int TotalFailures => _rounds.Sum(r => r.Failures); public double SuccessRate => TotalSuccess / (double)(TotalSuccess + TotalFailures) * 100; public IReadOnlyList<( int Round, long TotalMs, int Success, int Failures, double AvgResponseMs )> Rounds => _rounds; } public class LoadTestResults { private readonly Dictionary _scenarios = new(); private readonly int _concurrency; private readonly int _rounds; public LoadTestResults(int concurrency, int rounds) { _concurrency = concurrency; _rounds = rounds; } public void AddScenario(string name, ScenarioResults results) { _scenarios[name] = results; } public void PrintSummary() { Console.WriteLine($"Overall Statistics:"); Console.WriteLine($" Concurrency: {_concurrency} requests/round"); Console.WriteLine($" Rounds: {_rounds}"); Console.WriteLine($" Total Requests: {_concurrency * _rounds * _scenarios.Count}"); Console.WriteLine(); foreach (var (scenarioName, results) in _scenarios) { Console.WriteLine($"Scenario: {scenarioName}"); Console.WriteLine($" Average Round Time: {results.AverageTotalTimeMs:F2} ms"); Console.WriteLine($" Average Response Time: {results.AverageResponseTimeMs:F2} ms"); Console.WriteLine($" Success Rate: {results.SuccessRate:F2}%"); Console.WriteLine( $" Total Success: {results.TotalSuccess}/{results.TotalSuccess + results.TotalFailures}" ); if (results.TotalFailures > 0) { Console.WriteLine($" ⚠️ Total Failures: {results.TotalFailures}"); } // Calculate requests per second var rps = _concurrency / (results.AverageTotalTimeMs / 1000.0); Console.WriteLine($" Throughput: {rps:F2} requests/second"); Console.WriteLine(); } // Overall success rate var totalSuccess = _scenarios.Values.Sum(r => r.TotalSuccess); var totalRequests = _scenarios.Values.Sum(r => r.TotalSuccess + r.TotalFailures); var overallSuccessRate = totalSuccess / (double)totalRequests * 100; Console.WriteLine($"Overall Success Rate: {overallSuccessRate:F2}%"); if (overallSuccessRate < 99.0) { Console.WriteLine( "⚠️ Warning: Success rate is below 99%. Check server logs for errors." ); } else { Console.WriteLine("✅ All tests passed with high success rate!"); } } }