Skip to content

Commit

Permalink
Add Client Benchmark
Browse files Browse the repository at this point in the history
  • Loading branch information
budcribar committed Sep 2, 2024
1 parent 846d20c commit b3ee7f4
Show file tree
Hide file tree
Showing 3 changed files with 377 additions and 0 deletions.
50 changes: 50 additions & 0 deletions src/Benchmarks/ClientBenchmark/Blog.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
ClientBenchmarks.CreateClientBenchmark: DefaultJob
Runtime = .NET 8.0.8 (8.0.824.36612), X64 RyuJIT AVX2; GC = Concurrent Workstation
Mean = 19.798 ms, StdErr = 1.473 ms (7.44%), N = 100, StdDev = 14.735 ms
Min = 0.363 ms, Q1 = 0.451 ms, Median = 32.087 ms, Q3 = 32.290 ms, Max = 32.615 ms
IQR = 31.839 ms, LowerFence = -47.308 ms, UpperFence = 80.049 ms
ConfidenceInterval = [14.801 ms; 24.795 ms] (CI 99.9%), Margin = 4.997 ms (25.24% of Mean)
Skewness = -0.43, Kurtosis = 1.26, MValue = 3.19
-------------------- Histogram --------------------
[-0.503 ms ; 7.831 ms) | @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
[ 7.831 ms ; 14.505 ms) | @@
[14.505 ms ; 24.447 ms) | @@@@@
[24.447 ms ; 32.780 ms) | @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
---------------------------------------------------

ClientBenchmarks.CreateAndReadClientBenchmark: DefaultJob
Runtime = .NET 8.0.8 (8.0.824.36612), X64 RyuJIT AVX2; GC = Concurrent Workstation
Mean = 846.975 us, StdErr = 2.062 us (0.24%), N = 13, StdDev = 7.436 us
Min = 834.617 us, Q1 = 842.163 us, Median = 843.979 us, Q3 = 853.402 us, Max = 858.243 us
IQR = 11.239 us, LowerFence = 825.304 us, UpperFence = 870.260 us
ConfidenceInterval = [838.070 us; 855.881 us] (CI 99.9%), Margin = 8.905 us (1.05% of Mean)
Skewness = 0.11, Kurtosis = 1.68, MValue = 2
-------------------- Histogram --------------------
[830.466 us ; 862.394 us) | @@@@@@@@@@@@@
---------------------------------------------------

// * Summary *

BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4112/23H2/2023Update/SunValley3)
12th Gen Intel Core i7-12700F, 1 CPU, 20 logical and 12 physical cores
.NET SDK 8.0.400
[Host] : .NET 8.0.8 (8.0.824.36612), X64 RyuJIT AVX2 [AttachedDebugger]
DefaultJob : .NET 8.0.8 (8.0.824.36612), X64 RyuJIT AVX2


| Method | Mean | Error | StdDev | Median |
|----------------------------- |------------:|------------:|-------------:|------------:|
| CreateClientBenchmark | 19,797.9 us | 4,997.34 us | 14,734.78 us | 32,086.6 us |
| CreateAndReadClientBenchmark | 847.0 us | 8.91 us | 7.44 us | 844.0 us |

// * Warnings *
MultimodalDistribution
ClientBenchmarks.CreateClientBenchmark: Default -> It seems that the distribution can have several modes (mValue = 3.19)
Environment
Summary -> Benchmark was executed with attached debugger
MinIterationTime
ClientBenchmarks.CreateClientBenchmark: Default -> The minimum observed iteration time is 23.203ms which is very small. It's recommended to increase it to at least 100ms using more operations.

// * Hints *
Outliers
ClientBenchmarks.CreateAndReadClientBenchmark: Default -> 2 outliers were removed (876.21 us, 883.15 us)
19 changes: 19 additions & 0 deletions src/Benchmarks/ClientBenchmark/ClientBenchmark.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Google.Protobuf" Version="3.28.0" />
<PackageReference Include="Grpc.Net.Client" Version="2.65.0" />
<PackageReference Include="Grpc.Net.Client.Web" Version="2.65.0" />
<Protobuf Include="..\..\src\Protos\webview.proto" GrpcServices="Client" Link="Protos\webview.proto" />

<ProjectReference Include="..\..\RemoteWebView\RemoteWebView.csproj" />
</ItemGroup>
</Project>
308 changes: 308 additions & 0 deletions src/Benchmarks/ClientBenchmark/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Running;
using Google.Protobuf;
using Grpc.Core;
using Grpc.Net.Client;
using Microsoft.Extensions.FileProviders;
using PeakSWC.RemoteWebView;
using System;
using System.Diagnostics;
using System.Text;

namespace ClientBenchmark
{
public class ClientBenchmarks
{
private string _testGuid;
private string _testFilePath;
private string _rootDirectory;
private string _testFileName = "wwwroot/css/site.css";
private WebViewIPC.WebViewIPCClient _client;
private BrowserIPC.BrowserIPCClient _browser;
private string randomString;
private HttpClient httpClient;
private string URL = "https://localhost:5001";
private bool _prodServer = true;

private static readonly char[] chars =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();

public static string GenerateRandomString(int length)
{
StringBuilder result = new StringBuilder(length);
Random random = new Random();

for (int i = 0; i < length; i++)
{
result.Append(chars[random.Next(chars.Length)]);
}

return result.ToString();
}
private void KillExistingProcesses(string processName)
{
try
{
foreach (var process in Process.GetProcessesByName(processName))
{
Console.WriteLine($"Killing process: {process.ProcessName} (ID: {process.Id})");
process.Kill();
process.WaitForExit(); // Optionally wait for the process to exit
}
}
catch (Exception ex)
{
Console.WriteLine($"Error killing process: {ex.Message}");
}
}

private async Task PollHttpRequest(HttpClient httpClient, string url)
{
bool serverStarted = false;
while (!serverStarted)
{
try
{
var response = await httpClient.GetAsync(url);
serverStarted = response.IsSuccessStatusCode;
}
catch
{
// Server is not ready yet, wait a bit before retrying
await Task.Delay(10);
}
}
}


[GlobalSetup]
public void Setup()

{
if (_prodServer)
KillExistingProcesses("RemoteWebViewService");

var processStartInfo = new ProcessStartInfo
{
#if DEBUG
FileName = @"..\..\..\..\..\RemoteWebViewService\bin\publishNoAuth\RemoteWebViewService.exe",
#else
FileName = @"..\..\..\..\..\..\..\..\..\RemoteWebViewService\bin\publishNoAuth\RemoteWebViewService.exe",
#endif
RedirectStandardOutput = true
};

var handler = new SocketsHttpHandler
{
PooledConnectionIdleTimeout = Timeout.InfiniteTimeSpan,
KeepAlivePingDelay = TimeSpan.FromSeconds(90),
KeepAlivePingTimeout = TimeSpan.FromSeconds(60),
EnableMultipleHttp2Connections = true
};


httpClient = new HttpClient(handler);

if (_prodServer)
Process.Start(processStartInfo);

PollHttpRequest(httpClient, URL).Wait();

_rootDirectory = Directory.CreateTempSubdirectory().FullName;
_testFilePath = Path.Combine(_rootDirectory, _testFileName); // Example path

// Create the directory if it doesn't exist
Directory.CreateDirectory(Path.GetDirectoryName(_testFilePath)!);

randomString = GenerateRandomString(102400);
File.WriteAllText(_testFilePath, randomString);

var channel = GrpcChannel.ForAddress(URL, new GrpcChannelOptions
{
HttpClient = httpClient
});

_client = new WebViewIPC.WebViewIPCClient(channel);
_browser = new BrowserIPC.BrowserIPCClient(channel);
}
// [Benchmark]
public void CreateClientBenchmark()
{
string id = Guid.NewGuid().ToString();
var response = _client.CreateWebView(new CreateWebViewRequest { Id = id });
_client.Shutdown(new IdMessageRequest { Id = id });
}

//[Benchmark]
public void CreateAndReadClientBenchmark()
{
string id = Guid.NewGuid().ToString();
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); // shutdown waiting 20 seconds for tasks to cancel
var response = _client.CreateWebView(new CreateWebViewRequest { Id = id });
// foreach (var message in response.ResponseStream.ReadAllAsync(cts.Token).ToBlockingEnumerable())
foreach (var message in response.ResponseStream.ReadAllAsync().ToBlockingEnumerable())
{
if (message.Response == "created:")
{
_client.Shutdown(new IdMessageRequest { Id = id });
break;
}

break;
}
}

// | CreateAndReadBrowserClientBenchmark | 313.2 ms | 6.08 ms | 9.10 ms | 512 byte message
// CreateAndReadBrowserClientBenchmark | 317.7 ms | 4.29 ms | 4.01 ms | 1024 byte message
// | CreateAndReadBrowserClientBenchmark | 310.8 ms | 4.23 ms | 3.95 ms | 1024
// | CreateAndReadBrowserClientBenchmark | 358.1 ms | 2.80 ms | 2.62 ms | 1000 messages x 10240 bytes
// | CreateAndReadBrowserClientBenchmark | 359.1 ms | 3.83 ms | 3.59 ms | 1000 messages x 10240 bytes
//[Benchmark]
public void CreateAndReadBrowserClientBenchmark()
{
int count = 0;
int max = 1000;
string id = Guid.NewGuid().ToString();
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); // shutdown waiting 20 seconds for tasks to cancel
var response = _client.CreateWebView(new CreateWebViewRequest { Id = id });
foreach (var message in response.ResponseStream.ReadAllAsync(cts.Token).ToBlockingEnumerable())
//foreach (var message in response.ResponseStream.ReadAllAsync().ToBlockingEnumerable())
{
if (message.Response == "created:")
{
_ = Task.Run(() => {
for (int i = 1; i <= max; i++)
_browser.SendMessage(new SendSequenceMessageRequest { ClientId = id, Id=id, Sequence = (uint)i, Message = $"Message {i} {randomString}", IsPrimary=true, Url="url", Cookies="" });
} );

}
else if (message.Response.StartsWith("Message"))
{
count++;
if (int.Parse (message.Response.Split(" ")[1]) == max)
{
_client.Shutdown(new IdMessageRequest { Id = id });
break;
}

}
}
Console.WriteLine("Read " + count.ToString() + " messages");
}


// | ReadFilesClientBenchmark | 677.8 ms | 13.48 ms | 13.85 ms | 1000 files of len
// | ReadFilesClientBenchmark | 757.7 ms | 14.05 ms | 14.43 ms | 1000 files of len 10240
// | ReadFilesClientBenchmark | 736.5 ms | 12.93 ms | 12.09 ms | 1000 files of len 10240
// | ReadFilesClientBenchmark | 1.213 s | 0.0240 s | 0.0329 s | 1000 files of len 102400
[Benchmark]
public void ReadFilesClientBenchmark()
{
int count = 0;
int max = 1000;
string id = Guid.NewGuid().ToString();
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30000)); // shutdown waiting 20 seconds for tasks to cancel
var response = _client.CreateWebView(new CreateWebViewRequest { Id = id, EnableMirrors=false, HtmlHostPath="wwwroot" });
var bytes = 0;
foreach (var message in response.ResponseStream.ReadAllAsync(cts.Token).ToBlockingEnumerable())
{
if (message.Response == "created:")
{
AttachFileReader(cts, id, new PhysicalFileProvider(_rootDirectory+"/wwwroot"));
string url = $"{URL}/{id}/{_testFileName}";
for (int i = 1; i <= max; i++) {
var data = httpClient.GetStringAsync(url).Result;
bytes += data.Length;
//Console.WriteLine(data);
count++;
}

_client.Shutdown(new IdMessageRequest { Id = id });
}

}
Console.WriteLine($"Read {count} files total bytes {bytes}");
}

private void AttachFileReader(CancellationTokenSource cts, string id, IFileProvider fileProvider)
{
_ = Task.Factory.StartNew(async () =>
{
var files = _client.FileReader();
try
{
await files.RequestStream.WriteAsync(new FileReadRequest { Id = id, Init = new() });

await foreach (var message in files.ResponseStream.ReadAllAsync(cts.Token))
{
try
{
var path = message.Path[(message.Path.IndexOf('/') + 1)..];

await files.RequestStream.WriteAsync(new FileReadRequest { Id = id, Length = new FileReadLengthRequest { Path = message.Path, Length = fileProvider.GetFileInfo(path).Length } });

using var stream = fileProvider.GetFileInfo(path).CreateReadStream() ?? null;
if (stream == null)
await files.RequestStream.WriteAsync(new FileReadRequest { Id = id, Data = new FileReadDataRequest { Path = message.Path, Data = ByteString.Empty } });
else
{
var buffer = new Byte[8 * 1024];
int bytesRead = 0;

while ((bytesRead = await stream.ReadAsync(buffer)) > 0)
{
ByteString bs = ByteString.CopyFrom(buffer, 0, bytesRead);
await files.RequestStream.WriteAsync(new FileReadRequest { Id = id, Data = new FileReadDataRequest { Path = message.Path, Data = bs } });
}
await files.RequestStream.WriteAsync(new FileReadRequest { Id = id, Data = new FileReadDataRequest { Path = message.Path, Data = ByteString.Empty } });
}

}
catch (FileNotFoundException)
{
Console.WriteLine("FileNotFoundException");
// TODO Warning to user?
await files.RequestStream.WriteAsync(new FileReadRequest { Id = id, Data = new FileReadDataRequest { Path = message.Path, Data = ByteString.Empty } });
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
//FireDisconnected(ex);
await files.RequestStream.WriteAsync(new FileReadRequest { Id = id, Data = new FileReadDataRequest { Path = message.Path, Data = ByteString.Empty } });
}
}
Console.WriteLine("Done reading files");
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
//FireDisconnected(ex);
}
}, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}

[GlobalCleanup]
public void Cleanup()
{
try
{
// Clean up any temporary files created
File.Delete(_testFilePath);
Directory.Delete(_testFilePath);
}
catch { }
}

#if DEBUG
static void Main(string[] args) => BenchmarkSwitcher.FromAssembly(typeof(ClientBenchmarks).Assembly).Run(args, new DebugInProcessConfig());
#else
public static void Main(string[] args)
{
var summary = BenchmarkRunner.Run<ClientBenchmarks>();
Console.WriteLine(summary);
Console.ReadLine();
}
#endif
}
}

0 comments on commit b3ee7f4

Please sign in to comment.