Skip to content

Instantly share code, notes, and snippets.

@jpcrs
Last active November 4, 2024 21:32
Show Gist options
  • Save jpcrs/ae38135f0890f25db11ea6a22a7c2ed5 to your computer and use it in GitHub Desktop.
Save jpcrs/ae38135f0890f25db11ea6a22a7c2ed5 to your computer and use it in GitHub Desktop.
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