mirror of
https://github.com/sshnet/SSH.NET.git
synced 2026-10-17 05:21:15 +08:00
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:
committed by
GitHub
parent
64b428f7bf
commit
0371af9aaf
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user