Integration benchmark tests (#1298)

* Integration benchmark tests

* Fixes

* Write with encoding

* Update src/Renci.SshNet/Common/SshDataStream.cs

Co-authored-by: Rob Hague <rob.hague00@gmail.com>

* Add more benchmarks

---------

Co-authored-by: Rob Hague <rob.hague00@gmail.com>
This commit is contained in:
Wojciech Nagórski
2024-01-28 11:05:42 +01:00
committed by GitHub
parent 64b428f7bf
commit 0371af9aaf
10 changed files with 386 additions and 1 deletions
+22
View File
@@ -88,6 +88,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "svg", "svg", "{92E7B1B8-4C7
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Renci.SshNet.Benchmarks", "test\Renci.SshNet.Benchmarks\Renci.SshNet.Benchmarks.csproj", "{CF6CA77F-E4B8-4522-B267-E3F555E2E7B1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Renci.SshNet.IntegrationBenchmarks", "test\Renci.SshNet.IntegrationBenchmarks\Renci.SshNet.IntegrationBenchmarks.csproj", "{6DFC1807-3F44-4302-A302-43F7D887C4E0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -196,6 +198,26 @@ Global
{CF6CA77F-E4B8-4522-B267-E3F555E2E7B1}.Release|x64.Build.0 = Release|Any CPU
{CF6CA77F-E4B8-4522-B267-E3F555E2E7B1}.Release|x86.ActiveCfg = Release|Any CPU
{CF6CA77F-E4B8-4522-B267-E3F555E2E7B1}.Release|x86.Build.0 = Release|Any CPU
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Debug|ARM.ActiveCfg = Debug|Any CPU
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Debug|ARM.Build.0 = Debug|Any CPU
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Debug|x64.ActiveCfg = Debug|Any CPU
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Debug|x64.Build.0 = Debug|Any CPU
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Debug|x86.ActiveCfg = Debug|Any CPU
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Debug|x86.Build.0 = Debug|Any CPU
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Release|Any CPU.Build.0 = Release|Any CPU
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Release|ARM.ActiveCfg = Release|Any CPU
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Release|ARM.Build.0 = Release|Any CPU
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Release|x64.ActiveCfg = Release|Any CPU
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Release|x64.Build.0 = Release|Any CPU
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Release|x86.ActiveCfg = Release|Any CPU
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
+9 -1
View File
@@ -126,9 +126,17 @@ namespace Renci.SshNet.Common
{
throw new ArgumentNullException(nameof(encoding));
}
#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER
ReadOnlySpan<char> value = s;
var count = encoding.GetByteCount(value);
var bytes = count <= 256 ? stackalloc byte[count] : new byte[count];
encoding.GetBytes(value, bytes);
Write((uint) count);
Write(bytes);
#else
var bytes = encoding.GetBytes(s);
WriteBinary(bytes, 0, bytes.Length);
#endif
}
/// <summary>
@@ -17,4 +17,5 @@
<ItemGroup>
<EmbeddedResource Include="..\Data\*" LinkBase="Data" />
</ItemGroup>
</Project>
@@ -0,0 +1,72 @@
[*.cs]
#### Sonar rules ####
# S125: Sections of code should not be commented out
https://rules.sonarsource.com/csharp/RSPEC-125/
dotnet_diagnostic.S125.severity = suggestion
# S1118: Utility classes should not have public constructors
# https://rules.sonarsource.com/csharp/RSPEC-1118/
dotnet_diagnostic.S1118.severity = suggestion
# S1450: Private fields only used as local variables in methods should become local variables
# https://rules.sonarsource.com/csharp/RSPEC-1450/
dotnet_diagnostic.S1450.severity = suggestion
# S4144: Methods should not have identical implementations
# https://rules.sonarsource.com/csharp/RSPEC-4144/
dotnet_diagnostic.S4144.severity = suggestion
#### SYSLIB diagnostics ####
#### StyleCop Analyzers rules ####
# SA1028: Code must not contain trailing whitespace
# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1028.md
dotnet_diagnostic.SA1028.severity = suggestion
# SA1414: Tuple types in signatures should have element names
https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1414.md
dotnet_diagnostic.SA1414.severity = suggestion
# SA1400: Access modifier must be declared
# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1400.md
dotnet_diagnostic.SA1400.severity = suggestion
# SA1401: Fields must be private
# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1401.md
dotnet_diagnostic.SA1401.severity = suggestion
# SA1411: Attribute constructor must not use unnecessary parenthesis
# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1411.md
dotnet_diagnostic.SA1411.severity = suggestion
# SA1505: Opening braces must not be followed by blank line
# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1505.md
dotnet_diagnostic.SA1505.severity = suggestion
# SA1512: Single line comments must not be followed by blank line
# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1512.md
dotnet_diagnostic.SA1512.severity = suggestion
# SA1513: Closing brace must be followed by blank line
# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md
dotnet_diagnostic.SA1513.severity = suggestion
#### Meziantou.Analyzer rules ####
# MA0003: Add parameter name to improve readability
# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0003.md
dotnet_diagnostic.MA0003.severity = suggestion
# MA0053: Make class sealed
# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0053.md
dotnet_diagnostic.MA0053.severity = suggestion
#### .NET Compiler Platform analysers rules ####
# CA2000: Dispose objects before losing scope
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2000
dotnet_diagnostic.CA2000.severity = suggestion
@@ -0,0 +1,19 @@
using Renci.SshNet.IntegrationTests.TestsFixtures;
namespace Renci.SshNet.IntegrationBenchmarks
{
public class IntegrationBenchmarkBase
{
#pragma warning disable CA1822 // Mark members as static
public async Task GlobalSetup()
{
await InfrastructureFixture.Instance.InitializeAsync().ConfigureAwait(false);
}
public async Task GlobalCleanup()
{
await InfrastructureFixture.Instance.DisposeAsync().ConfigureAwait(false);
}
#pragma warning restore CA1822 // Mark members as static
}
}
@@ -0,0 +1,12 @@
using BenchmarkDotNet.Running;
namespace Renci.SshNet.IntegrationBenchmarks
{
public static class Program
{
public static void Main(string[] args)
{
_ = BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
}
}
}
@@ -0,0 +1,25 @@
<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.13.9" />
<!-- <PackageReference Include="SSH.NET" Version="2023.0.0" /> -->
<PackageReference Include="SSH.NET" Version="2023.0.0" ExcludeAssets="All" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Renci.SshNet\Renci.SshNet.csproj" />
<ProjectReference Include="..\Renci.SshNet.IntegrationTests\Renci.SshNet.IntegrationTests.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\Data\*" LinkBase="Data" />
</ItemGroup>
</Project>
@@ -0,0 +1,79 @@
using System.Text;
using BenchmarkDotNet.Attributes;
using Renci.SshNet.IntegrationTests.TestsFixtures;
namespace Renci.SshNet.IntegrationBenchmarks
{
[MemoryDiagnoser]
[SimpleJob]
public class ScpClientBenchmark : IntegrationBenchmarkBase
{
private readonly InfrastructureFixture _infrastructureFixture;
private readonly string _file = $"/tmp/{Guid.NewGuid()}.txt";
private ScpClient? _scpClient;
private MemoryStream? _uploadStream;
public ScpClientBenchmark()
{
_infrastructureFixture = InfrastructureFixture.Instance;
}
[GlobalSetup]
public async Task Setup()
{
await GlobalSetup().ConfigureAwait(false);
_scpClient = new ScpClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password);
await _scpClient.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
var fileContent = "File content !@#$%^&*()_+{}:,./<>[];'\\|";
_uploadStream = new MemoryStream(Encoding.UTF8.GetBytes(fileContent));
}
[GlobalCleanup]
public async Task Cleanup()
{
await GlobalCleanup().ConfigureAwait(false);
await _uploadStream!.DisposeAsync().ConfigureAwait(false);
}
[Benchmark]
public void Connect()
{
using var scpClient = new ScpClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password);
scpClient.Connect();
}
[Benchmark]
public async Task ConnectAsync()
{
using var scpClient = new ScpClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password);
await scpClient.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
}
[Benchmark]
public string ConnectUploadAndDownload()
{
using var scpClient = new ScpClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password);
scpClient.Connect();
_uploadStream!.Position = 0;
scpClient.Upload(_uploadStream, _file);
using var downloadStream = new MemoryStream();
scpClient.Download(_file, downloadStream);
return Encoding.UTF8.GetString(downloadStream.ToArray());
}
[Benchmark]
public string UploadAndDownload()
{
_uploadStream!.Position = 0;
_scpClient!.Upload(_uploadStream, _file);
using var downloadStream = new MemoryStream();
_scpClient.Download(_file, downloadStream);
return Encoding.UTF8.GetString(downloadStream.ToArray());
}
}
}
@@ -0,0 +1,78 @@
using System.Text;
using BenchmarkDotNet.Attributes;
using Renci.SshNet.IntegrationTests.TestsFixtures;
using Renci.SshNet.Sftp;
namespace Renci.SshNet.IntegrationBenchmarks
{
[MemoryDiagnoser]
[SimpleJob]
public class SftpClientBenchmark : IntegrationBenchmarkBase
{
private readonly InfrastructureFixture _infrastructureFixture;
private readonly string _file = $"/tmp/{Guid.NewGuid()}.txt";
private SftpClient? _sftpClient;
private MemoryStream? _uploadStream;
public SftpClientBenchmark()
{
_infrastructureFixture = InfrastructureFixture.Instance;
}
[GlobalSetup]
public async Task Setup()
{
await GlobalSetup().ConfigureAwait(false);
_sftpClient = new SftpClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password);
await _sftpClient.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
var fileContent = "File content !@#$%^&*()_+{}:,./<>[];'\\|";
_uploadStream = new MemoryStream(Encoding.UTF8.GetBytes(fileContent));
}
[GlobalCleanup]
public async Task Cleanup()
{
await GlobalCleanup().ConfigureAwait(false);
await _uploadStream!.DisposeAsync().ConfigureAwait(false);
}
[Benchmark]
public void Connect()
{
using var sftpClient = new SftpClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password);
sftpClient.Connect();
}
[Benchmark]
public async Task ConnectAsync()
{
using var sftpClient = new SftpClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password);
await sftpClient.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
}
public IEnumerable<ISftpFile> ListDirectory()
{
return _sftpClient!.ListDirectory("/root");
}
public IAsyncEnumerable<ISftpFile> ListDirectoryAsync()
{
return _sftpClient!.ListDirectoryAsync("/root", CancellationToken.None);
}
[Benchmark]
public string UploadAndDownload()
{
_uploadStream!.Position = 0;
_sftpClient!.UploadFile(_uploadStream, _file);
using var downloadStream = new MemoryStream();
_sftpClient.DownloadFile(_file, downloadStream);
return Encoding.UTF8.GetString(downloadStream.ToArray());
}
}
}
@@ -0,0 +1,69 @@
using BenchmarkDotNet.Attributes;
using Renci.SshNet.IntegrationTests.TestsFixtures;
namespace Renci.SshNet.IntegrationBenchmarks
{
[MemoryDiagnoser]
[SimpleJob]
public class SshClientBenchmark : IntegrationBenchmarkBase
{
private readonly InfrastructureFixture _infrastructureFixture;
private SshClient? _sshClient;
public SshClientBenchmark()
{
_infrastructureFixture = InfrastructureFixture.Instance;
}
[GlobalSetup]
public async Task Setup()
{
await GlobalSetup().ConfigureAwait(false);
_sshClient = new SshClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password);
await _sshClient.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
}
[GlobalCleanup]
public async Task Cleanup()
{
await GlobalCleanup().ConfigureAwait(false);
}
[Benchmark]
public void Connect()
{
using var sshClient = new SshClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password);
sshClient.Connect();
}
[Benchmark]
public async Task ConnectAsync()
{
using var sshClient = new SshClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password);
await sshClient.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
}
[Benchmark]
public string ConnectAndRunCommand()
{
using var sshClient = new SshClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password);
sshClient.Connect();
return sshClient.RunCommand("echo $'test !@#$%^&*()_+{}:,./<>[];\\|'").Result;
}
[Benchmark]
public async Task<string> ConnectAsyncAndRunCommand()
{
using var sshClient = new SshClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password);
await sshClient.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
return sshClient.RunCommand("echo $'test !@#$%^&*()_+{}:,./<>[];\\|'").Result;
}
[Benchmark]
public string RunCommand()
{
return _sshClient!.RunCommand("echo $'test !@#$%^&*()_+{}:,./<>[];\\|'").Result;
}
}
}