Last active
November 4, 2024 21:32
-
-
Save jpcrs/ae38135f0890f25db11ea6a22a7c2ed5 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Program | |
{ | |
static long _totalRequests = 0; | |
public static async Task Main(string[] args) | |
{ | |
await RunAsync(1, 1, 500_000); | |
} | |
static async Task RunAsync(int delayBetweenRequests, int rampUpInterval, int rampUpUnits) | |
{ | |
var tasks = new List<Task>(); | |
var ids = GetIds(); | |
tasks.Add(MonitorRequestRateAsync()); | |
foreach (var batch in ids.Chunk(rampUpUnits)) | |
{ | |
foreach (var id in batch) | |
{ | |
tasks.Add(SimulateRequestAsync(delayBetweenRequests, id)); | |
} | |
await Task.Delay(rampUpInterval * 1000); | |
} | |
await Task.WhenAll(tasks); | |
} | |
static List<int> GetIds() | |
{ | |
return Enumerable.Range(0, 1_000_000).ToList(); | |
} | |
static async Task SimulateRequestAsync(int delayBetweenRequestsSec, int id) | |
{ | |
while (true) | |
{ | |
Interlocked.Increment(ref _totalRequests); | |
await Task.Delay((delayBetweenRequestsSec * 1000) + new Random().Next(-500, 500)); | |
} | |
} | |
static async Task MonitorRequestRateAsync() | |
{ | |
var metrics = new LoadTestMetrics(); | |
var renderer = new ConsoleRenderer(metrics); | |
metrics.AddLog($"Starting new batch of requests"); | |
while (true) | |
{ | |
await Task.Delay(1000); | |
var currentCount = Interlocked.Read(ref _totalRequests); | |
metrics.UpdateMetrics(currentCount); | |
renderer.Render(); | |
} | |
} | |
} | |
public class LoadTestMetrics | |
{ | |
public DateTime StartTime { get; } | |
public long TotalRequests { get; private set; } | |
public Queue<long> RequestHistory { get; } | |
public long CurrentRps { get; private set; } | |
public Queue<(DateTime Time, string Message)> Logs { get; } | |
private const int MaxLogEntries = 10; | |
public LoadTestMetrics() | |
{ | |
StartTime = DateTime.Now; | |
RequestHistory = new Queue<long>(10); | |
Logs = new Queue<(DateTime, string)>(MaxLogEntries); | |
TotalRequests = 0; | |
} | |
public void UpdateMetrics(long newTotalRequests) | |
{ | |
CurrentRps = newTotalRequests - TotalRequests; | |
TotalRequests = newTotalRequests; | |
RequestHistory.Enqueue(CurrentRps); | |
AddLog($"Updating metrics."); | |
if (RequestHistory.Count > 10) RequestHistory.Dequeue(); | |
} | |
public void AddLog(string message) | |
{ | |
Logs.Enqueue((DateTime.Now, message)); | |
if (Logs.Count > MaxLogEntries) Logs.Dequeue(); | |
} | |
} | |
using Spectre.Console; | |
public class ConsoleRenderer(LoadTestMetrics metrics) | |
{ | |
public void Render() | |
{ | |
AnsiConsole.Clear(); | |
RenderHeader(); | |
RenderStatsTable(); | |
AnsiConsole.WriteLine(); | |
RenderChart(); | |
RenderTrend(); | |
AnsiConsole.WriteLine(); | |
RenderLogs(); | |
} | |
private void RenderHeader() | |
{ | |
var header = new Panel($"[bold white]Load Test Monitor[/]\n[grey]Started at {metrics.StartTime:HH:mm:ss}[/]") | |
.Padding(1, 1) | |
.Border(BoxBorder.Rounded) | |
.BorderStyle(new Style(Color.Blue)); | |
AnsiConsole.Write(header); | |
} | |
private void RenderStatsTable() | |
{ | |
var table = new Table() | |
.Border(TableBorder.Rounded) | |
.BorderStyle(new Style(Color.Grey)) | |
.AddColumn(new TableColumn("[bold]Metric[/]").Centered()) | |
.AddColumn(new TableColumn("[bold]Value[/]").Centered()); | |
table.AddRow("Current RPS", $"[green]{metrics.CurrentRps:N0}[/]"); | |
table.AddRow("Average RPS", $"[blue]{metrics.RequestHistory.Average():N0}[/]"); | |
table.AddRow("Peak RPS", $"[red]{metrics.RequestHistory.Max():N0}[/]"); | |
table.AddRow("Total Requests", $"[yellow]{metrics.TotalRequests:N0}[/]"); | |
table.AddRow("Monitor Duration", $"[grey]{(DateTime.Now - metrics.StartTime).ToString(@"hh\:mm\:ss")}[/]"); | |
AnsiConsole.Write(table); | |
} | |
private void RenderChart() | |
{ | |
var maxValue = Math.Max(100, metrics.RequestHistory.Max()); | |
var chart = new BarChart() | |
.Width(100) | |
.Label("[bold green]Requests per Second (Last 10 seconds)[/]") | |
.CenterLabel() | |
.AddItems(metrics.RequestHistory.Select((value, index) => { | |
var secondsAgo = metrics.RequestHistory.Count - index - 1; | |
var color = secondsAgo == 0 ? Color.Red : | |
value >= metrics.RequestHistory.Average() ? Color.Green : | |
Color.Blue; | |
return new BarChartItem( | |
secondsAgo % 5 == 0 ? $"{secondsAgo}s" : "", | |
value, | |
color); | |
}).Reverse()); | |
var chartPanel = new Panel(chart) | |
.Border(BoxBorder.Rounded) | |
.BorderStyle(new Style(Color.Grey)) | |
.Padding(1, 1); | |
AnsiConsole.Write(chartPanel); | |
} | |
private void RenderTrend() | |
{ | |
var history = metrics.RequestHistory; | |
var trend = history.Count > 5 ? | |
history.Skip(history.Count - 5).Average() - history.Take(10).Average() : 0; | |
var trendSymbol = trend > 0 ? "[green]↑[/]" : trend < 0 ? "[red]↓[/]" : "[grey]→[/]"; | |
AnsiConsole.MarkupLine($"\nTrend (last 5s): {trendSymbol} {Math.Abs(trend):N2} RPS"); | |
} | |
private void RenderLogs() | |
{ | |
var logPanel = new Panel(GetFormattedLogs()) | |
.Header("[bold]Recent Logs[/]") | |
.Padding(1, 1) | |
.Border(BoxBorder.Rounded) | |
.BorderStyle(new Style(Color.Grey)); | |
AnsiConsole.Write(logPanel); | |
} | |
private string GetFormattedLogs() | |
{ | |
if (!metrics.Logs.Any()) | |
return "[grey]No logs available[/]"; | |
return string.Join("\n", metrics.Logs.Reverse().Select(log => | |
$"[grey]{log.Time:HH:mm:ss}[/] {log.Message}")); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment