Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Plugins and improve Thread startup #38

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions Netling.Client/MainWindow.xaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
<Window x:Class="Netling.Client.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Netling" Width="500" Height="223" ResizeMode="NoResize">
Title="Netling" Width="500" Height="342" ResizeMode="NoResize">
<Grid Margin="10">
<StackPanel Orientation="Horizontal">
<StackPanel Margin="0,0,20,0">
<TextBlock Text="Threads" VerticalAlignment="Top" HorizontalAlignment="Left"/>
<ComboBox x:Name="Threads" VerticalAlignment="Top" HorizontalAlignment="Left" Width="120" Height="25" Margin="0,5,0,0" />
<Grid Height="100"/>
</StackPanel>
<StackPanel Margin="0,0,20,0">
<TextBlock Text="Duration" VerticalAlignment="Top" HorizontalAlignment="Left"/>
Expand All @@ -23,11 +24,28 @@
<ComboBoxItem>3000 runs on 1 thread</ComboBoxItem>
<ComboBoxItem>10000 runs on 1 thread</ComboBoxItem>
</ComboBox>

</StackPanel>
<StackPanel Margin="0,0,20,0">
<TextBlock Text="Warmup" VerticalAlignment="Top" HorizontalAlignment="Left"/>
<ComboBox x:Name="Warmup" VerticalAlignment="Top" HorizontalAlignment="Left" Width="120" Height="25" Margin="0,5,0,0">
<ComboBoxItem IsSelected="True">2 seconds</ComboBoxItem>
<ComboBoxItem>0 seconds</ComboBoxItem>
<ComboBoxItem>2 seconds</ComboBoxItem>
<ComboBoxItem>10 seconds</ComboBoxItem>
<ComboBoxItem>20 seconds</ComboBoxItem>
<ComboBoxItem>30 seconds</ComboBoxItem>
<ComboBoxItem>1 minute</ComboBoxItem>
<ComboBoxItem>2 minutes</ComboBoxItem>
</ComboBox>

</StackPanel>
</StackPanel>

<TextBlock Text="URL" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="0,61,0,0"/>
<TextBox x:Name="Url" VerticalAlignment="Stretch" KeyUp="Urls_OnKeyUp" HorizontalAlignment="Stretch" Padding="6" Margin="0,82,0,50" />
<TextBox x:Name="Url" KeyUp="Urls_OnKeyUp" Padding="6" Margin="0,82,0,166" />

<ListBox x:Name="Plugins" Margin="0,153,0,48"/>

<Button Content="Run" x:Name="StartButton" Background="#ff0079c5" BorderThickness="0" Foreground="White" Click="StartButton_Click" VerticalAlignment="Bottom" HorizontalAlignment="Left" Width="100" Height="30"/>
<ProgressBar x:Name="StatusProgressbar" VerticalAlignment="Bottom" Minimum="0" Maximum="100" HorizontalAlignment="Stretch" Visibility="Hidden" Height="16" Margin="120,0,0,7" />
Expand Down
56 changes: 51 additions & 5 deletions Netling.Client/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Netling.Core;
using Netling.Core.HttpClientWorker;
using Netling.Core.Models;
using Netling.Core.SocketWorker;

Expand All @@ -21,7 +25,6 @@ public partial class MainWindow
private List<ResultWindow> _resultWindows;
private ResultWindowItem _baselineResult;


public MainWindow()
{
_resultWindows = new List<ResultWindow>();
Expand Down Expand Up @@ -49,16 +52,22 @@ private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)

Threads.SelectedIndex = 0;
Url.Focus();

List<PluginInfo> pluginList = PluginLoader.GetPluginList(".");
Plugins.ItemsSource = pluginList.Select(x => string.Format("{0} ({1})", x.TypeName, x.AssemblyShort));
Plugins.Tag = pluginList;
}

private async void StartButton_Click(object sender, RoutedEventArgs e)
{
if (!_running)
{
var duration = default(TimeSpan);
var warmup = TimeSpan.FromSeconds(0);
int? count = null;
var threads = Convert.ToInt32(((KeyValuePair<int, string>)Threads.SelectionBoxItem).Key);
var durationText = (string)((ComboBoxItem)Duration.SelectedItem).Content;
var warmupText = (string)((ComboBoxItem)Warmup.SelectedItem).Content;
StatusProgressbar.IsIndeterminate = false;

switch (durationText)
Expand Down Expand Up @@ -102,7 +111,31 @@ private async void StartButton_Click(object sender, RoutedEventArgs e)
count = 10000;
StatusProgressbar.IsIndeterminate = true;
break;

}

switch (warmupText)
{
case "0 seconds":
warmup = TimeSpan.FromSeconds(0);
break;
case "2 seconds":
warmup = TimeSpan.FromSeconds(2);
break;
case "10 seconds":
warmup = TimeSpan.FromSeconds(10);
break;
case "20 seconds":
warmup = TimeSpan.FromSeconds(20);
break;
case "30 seconds":
warmup = TimeSpan.FromSeconds(30);
break;
case "1 minute":
warmup = TimeSpan.FromMinutes(1);
break;
case "2 minutes":
warmup = TimeSpan.FromMinutes(2);
break;
}

if (string.IsNullOrWhiteSpace(Url.Text))
Expand All @@ -115,6 +148,11 @@ private async void StartButton_Click(object sender, RoutedEventArgs e)
return;
}

if (Plugins.SelectedIndex == -1)
{
return;
}

Threads.IsEnabled = false;
Duration.IsEnabled = false;
Url.IsEnabled = false;
Expand All @@ -127,15 +165,23 @@ private async void StartButton_Click(object sender, RoutedEventArgs e)
StatusProgressbar.Value = 0;
StatusProgressbar.Visibility = Visibility.Visible;

var worker = new Worker(new SocketWorkerJob(uri));
List<PluginInfo> pluginList = Plugins.Tag as List<PluginInfo>;
PluginInfo selectedPlugin = pluginList[Plugins.SelectedIndex];

Assembly workerPluginAssembly = PluginLoader.LoadPlugin(selectedPlugin.AssemblyName);
var workerPlugin = PluginLoader.CreateInstance(workerPluginAssembly, selectedPlugin.TypeName);

workerPlugin.Initialize(uri);

var worker = new Worker(workerPlugin);

if (count.HasValue)
{
_task = worker.Run(uri.ToString(), count.Value, cancellationToken);
_task = worker.Run(uri.ToString(), count.Value, warmup, cancellationToken);
}
else
{
_task = worker.Run(uri.ToString(), threads, duration, cancellationToken);
_task = worker.Run(uri.ToString(), threads, duration, warmup, cancellationToken);
}

_task.GetAwaiter().OnCompleted(async () =>
Expand Down
106 changes: 79 additions & 27 deletions Netling.ConsoleClient/Program.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using CommandLine.Options;
using Netling.Core;
using Netling.Core.HttpClientWorker;
using Netling.Core.Models;
using Netling.Core.SocketWorker;

Expand All @@ -20,29 +23,51 @@ static void Main(string[] args)
var threads = 1;
var duration = 10;
int? count = null;
int? warmupOpt = null;
int warmup = 0;
string testname = string.Empty;

var p = new OptionSet()
{
{"t|threads=", (int v) => threads = v},
{"d|duration=", (int v) => duration = v},
{"c|count=", (int? v) => count = v}
{"c|count=", (int? v) => count = v},
{"w|warmup=", (int? w) => warmupOpt = w},
{"n|testname=", (string n) => testname = n},
};

var extraArgs = p.Parse(args);
var url = extraArgs.FirstOrDefault(e => e.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || e.StartsWith("https://", StringComparison.OrdinalIgnoreCase));
Uri uri = null;
PluginInfo pluginTest = null;

if (string.IsNullOrEmpty(testname))
{
ShowHelp();
return;
}
else
{
List<PluginInfo> pluginList = PluginLoader.GetPluginList(".");
pluginTest = pluginList.Where(x => x.TypeName == testname).FirstOrDefault();
}

if (warmupOpt.HasValue)
{
warmup = warmupOpt.Value;
}

if (url != null && !Uri.TryCreate(url, UriKind.Absolute, out uri))
{
Console.WriteLine("Failed to parse URL");
}
else if (url != null && count.HasValue)
else if (pluginTest != null && url != null && count.HasValue)
{
RunWithCount(uri, count.Value).Wait();
RunWithCount(uri, pluginTest, count.Value, TimeSpan.FromSeconds(warmup)).Wait();
}
else if (url != null)
else if (pluginTest != null && url != null)
{
RunWithDuration(uri, threads, TimeSpan.FromSeconds(duration)).Wait();
RunWithDuration(uri, pluginTest, threads, TimeSpan.FromSeconds(duration), TimeSpan.FromSeconds(warmup)).Wait();
}
else
{
Expand All @@ -53,44 +78,64 @@ static void Main(string[] args)
private static void ShowHelp()
{
Console.WriteLine(HelpString);

List<PluginInfo> pluginList = PluginLoader.GetPluginList(".");

Console.WriteLine("Plugins Availiable:");
foreach (var plugin in pluginList)
{
Console.WriteLine("{0} {1}", plugin.TypeName, plugin.AssemblyShort);
}
}

private static Task RunWithCount(Uri uri, int count)
private static Task RunWithCount(Uri uri, PluginInfo selectedPlugin, int count, TimeSpan warmupDuration)
{
Console.WriteLine(StartRunWithCountString, count, uri);
return Run(uri, 1, TimeSpan.MaxValue, count);
return Run(uri, selectedPlugin, 1, TimeSpan.MaxValue, warmupDuration, count);
}

private static Task RunWithDuration(Uri uri, int threads, TimeSpan duration)
private static Task RunWithDuration(Uri uri, PluginInfo selectedPlugin, int threads, TimeSpan duration, TimeSpan warmupDuration)
{
Console.WriteLine(StartRunWithDurationString, duration.TotalSeconds, uri, threads);
return Run(uri, threads, duration, null);
Console.WriteLine(StartRunWithDurationString, duration.TotalSeconds, warmupDuration, uri, threads);
return Run(uri, selectedPlugin, threads, duration, warmupDuration, null);
}

private static async Task Run(Uri uri, int threads, TimeSpan duration, int? count)
private static async Task Run(Uri uri, PluginInfo selectedPlugin, int threads, TimeSpan duration, TimeSpan warmupDuration, int? count)
{
WorkerResult result;
var worker = new Worker(new SocketWorkerJob(uri));

Console.WriteLine("Running tests from {0} : {1}", selectedPlugin.AssemblyShort, selectedPlugin.TypeName);

Assembly workerPluginAssembly = PluginLoader.LoadPlugin(selectedPlugin.AssemblyName);
var workerPlugin = PluginLoader.CreateInstance(workerPluginAssembly, selectedPlugin.TypeName);
workerPlugin.Initialize(uri);

var worker = new Worker(workerPlugin);

if (count.HasValue)
{
result = await worker.Run(uri.ToString(), count.Value, new CancellationToken());
result = await worker.Run(uri.ToString(), count.Value, warmupDuration, new CancellationToken());
}
else
{
result = await worker.Run(uri.ToString(), threads, duration, new CancellationToken());
result = await worker.Run(uri.ToString(), threads, duration, warmupDuration, new CancellationToken());
}

Console.WriteLine(ResultString,
Console.WriteLine(ResultString1,
result.Count,
result.Elapsed.TotalSeconds,
result.RequestsPerSecond,
result.Bandwidth,
result.Errors,
result.RequestsPerSecond,
result.Bandwidth,
result.Errors);

Console.WriteLine(ResultString2,
result.Median,
result.StdDev,
result.Min,
result.Max,
result.P99,
result.P95,
result.P50,
GetAsciiHistogram(result));
}

Expand Down Expand Up @@ -122,12 +167,14 @@ private static string GetAsciiHistogram(WorkerResult workerResult)
}

private const string HelpString = @"
Usage: netling [-t threads] [-d duration] url
Usage: netling [-t threads] [-d duration] [-w warmupSec] -n plugin url

Options:
-t count Number of threads to spawn.
-d count Duration of the run in seconds.
-w warmup Duration of the warmup phase in seconds.
-c count Amount of requests to send on a single thread.
-n pluginname Name of plugin type to use for testing.

Examples:
netling http://localhost:5000/
Expand All @@ -141,18 +188,23 @@ private static string GetAsciiHistogram(WorkerResult workerResult)
private const string StartRunWithDurationString = @"
Running {0}s test with {2} threads @ {1}";

private const string ResultString = @"
private const string ResultString1 = @"
{0} requests in {1:0.##}s
Requests/sec: {2:0}
Bandwidth: {3:0} mbit
Errors: {4:0}
Latency
Median: {5:0.000} ms
StdDev: {6:0.000} ms
Min: {7:0.000} ms
Max: {8:0.000} ms
Errors: {4:0}";

{9}
private const string ResultString2 = @"
Latency
Median: {0:0.000} ms
StdDev: {1:0.000} ms
Min: {2:0.000} ms
Max: {3:0.000} ms
P99: {4:0.000} ms
P95: {5:0.000} ms
P50: {6:0.000} ms

{7}
";
}
}
13 changes: 11 additions & 2 deletions Netling.Core.HttpClientWorker/HttpClientWorkerJob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,29 @@ namespace Netling.Core.HttpClientWorker
public class HttpClientWorkerJob : IWorkerJob
{
private readonly int _index;
private readonly Uri _uri;
private Uri _uri;
private readonly Stopwatch _stopwatch;
private readonly Stopwatch _localStopwatch;
private readonly WorkerThreadResult _workerThreadResult;
private readonly HttpClient _httpClient;

// Used to approximately calculate bandwidth
private static readonly int MissingHeaderLength = "HTTP/1.1 200 OK\r\nContent-Length: 123\r\nContent-Type: text/plain\r\n\r\n".Length;
private static readonly int MissingHeaderLength = "HTTP/1.1 200 OK\r\nContent-Length: 123\r\nContent-Type: text/plain\r\n\r\n".Length;

public HttpClientWorkerJob()
{
}

public HttpClientWorkerJob(Uri uri)
{
_uri = uri;
}

public void Initialize(Uri uri)
{
_uri = uri;
}

private HttpClientWorkerJob(int index, Uri uri, WorkerThreadResult workerThreadResult)
{
_index = index;
Expand Down
Loading