From aec4168496e16bff5c17aa37b90f427b0c99f96a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Nag=C3=B3rski?= Date: Wed, 6 Dec 2023 20:59:35 +0100 Subject: [PATCH 1/5] Integration benchmark tests --- .../IntegrationBenchmarkBase.cs | 18 +++++++++ .../IntegrationBenchmarks/Test.cs | 38 +++++++++++++++++++ .../Renci.SshNet.Benchmarks.csproj | 5 ++- 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 test/Renci.SshNet.Benchmarks/IntegrationBenchmarks/IntegrationBenchmarkBase.cs create mode 100644 test/Renci.SshNet.Benchmarks/IntegrationBenchmarks/Test.cs diff --git a/test/Renci.SshNet.Benchmarks/IntegrationBenchmarks/IntegrationBenchmarkBase.cs b/test/Renci.SshNet.Benchmarks/IntegrationBenchmarks/IntegrationBenchmarkBase.cs new file mode 100644 index 000000000..d37937374 --- /dev/null +++ b/test/Renci.SshNet.Benchmarks/IntegrationBenchmarks/IntegrationBenchmarkBase.cs @@ -0,0 +1,18 @@ +using Renci.SshNet.IntegrationTests.TestsFixtures; + +namespace Renci.SshNet.Benchmarks.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 + } +} diff --git a/test/Renci.SshNet.Benchmarks/IntegrationBenchmarks/Test.cs b/test/Renci.SshNet.Benchmarks/IntegrationBenchmarks/Test.cs new file mode 100644 index 000000000..200530e2f --- /dev/null +++ b/test/Renci.SshNet.Benchmarks/IntegrationBenchmarks/Test.cs @@ -0,0 +1,38 @@ +using BenchmarkDotNet.Attributes; + +using Renci.SshNet.IntegrationTests.TestsFixtures; + +namespace Renci.SshNet.Benchmarks.IntegrationBenchmarks +{ + [MemoryDiagnoser] + [SimpleJob] + public class Test : IntegrationBenchmarkBase + { + private readonly InfrastructureFixture _infrastructureFixture; + + [GlobalSetup] + public async Task Setup() + { + await GlobalSetup().ConfigureAwait(false); + } + + [GlobalCleanup] + public async Task Cleanup() + { + await GlobalCleanup().ConfigureAwait(false); + } + + public Test() + { + _infrastructureFixture = InfrastructureFixture.Instance; + } + + [Benchmark] + public string Connect() + { + using var sshClient = new SshClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password); + sshClient.Connect(); + return sshClient.RunCommand("echo $'test !@#$%^&*()_+{}:,./<>[];\\|'").Result; + } + } +} diff --git a/test/Renci.SshNet.Benchmarks/Renci.SshNet.Benchmarks.csproj b/test/Renci.SshNet.Benchmarks/Renci.SshNet.Benchmarks.csproj index eee47fcf7..8b096e638 100644 --- a/test/Renci.SshNet.Benchmarks/Renci.SshNet.Benchmarks.csproj +++ b/test/Renci.SshNet.Benchmarks/Renci.SshNet.Benchmarks.csproj @@ -1,4 +1,4 @@ - + Exe net8.0 @@ -8,10 +8,13 @@ + + + From 98694092729a7a4e14c0c83c9b7f3650c60b2f18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Nag=C3=B3rski?= Date: Sat, 27 Jan 2024 15:59:57 +0100 Subject: [PATCH 2/5] Fixes --- Renci.SshNet.sln | 22 ++++++ .../Renci.SshNet.Benchmarks.csproj | 2 +- .../.editorconfig | 72 +++++++++++++++++++ .../IntegrationBenchmarkBase.cs | 3 +- .../Program.cs | 12 ++++ .../Renci.SshNet.IntegrationBenchmarks.csproj | 25 +++++++ .../SshClientBenchmark.cs} | 14 ++-- 7 files changed, 141 insertions(+), 9 deletions(-) create mode 100644 test/Renci.SshNet.IntegrationBenchmarks/.editorconfig rename test/{Renci.SshNet.Benchmarks/IntegrationBenchmarks => Renci.SshNet.IntegrationBenchmarks}/IntegrationBenchmarkBase.cs (90%) create mode 100644 test/Renci.SshNet.IntegrationBenchmarks/Program.cs create mode 100644 test/Renci.SshNet.IntegrationBenchmarks/Renci.SshNet.IntegrationBenchmarks.csproj rename test/{Renci.SshNet.Benchmarks/IntegrationBenchmarks/Test.cs => Renci.SshNet.IntegrationBenchmarks/SshClientBenchmark.cs} (87%) diff --git a/Renci.SshNet.sln b/Renci.SshNet.sln index 0ca62c338..40103f6d6 100644 --- a/Renci.SshNet.sln +++ b/Renci.SshNet.sln @@ -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 diff --git a/test/Renci.SshNet.Benchmarks/Renci.SshNet.Benchmarks.csproj b/test/Renci.SshNet.Benchmarks/Renci.SshNet.Benchmarks.csproj index 8b096e638..3e2b270c1 100644 --- a/test/Renci.SshNet.Benchmarks/Renci.SshNet.Benchmarks.csproj +++ b/test/Renci.SshNet.Benchmarks/Renci.SshNet.Benchmarks.csproj @@ -14,10 +14,10 @@ - + diff --git a/test/Renci.SshNet.IntegrationBenchmarks/.editorconfig b/test/Renci.SshNet.IntegrationBenchmarks/.editorconfig new file mode 100644 index 000000000..e903a76d8 --- /dev/null +++ b/test/Renci.SshNet.IntegrationBenchmarks/.editorconfig @@ -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 diff --git a/test/Renci.SshNet.Benchmarks/IntegrationBenchmarks/IntegrationBenchmarkBase.cs b/test/Renci.SshNet.IntegrationBenchmarks/IntegrationBenchmarkBase.cs similarity index 90% rename from test/Renci.SshNet.Benchmarks/IntegrationBenchmarks/IntegrationBenchmarkBase.cs rename to test/Renci.SshNet.IntegrationBenchmarks/IntegrationBenchmarkBase.cs index d37937374..5df3990c1 100644 --- a/test/Renci.SshNet.Benchmarks/IntegrationBenchmarks/IntegrationBenchmarkBase.cs +++ b/test/Renci.SshNet.IntegrationBenchmarks/IntegrationBenchmarkBase.cs @@ -1,6 +1,6 @@ using Renci.SshNet.IntegrationTests.TestsFixtures; -namespace Renci.SshNet.Benchmarks.IntegrationBenchmarks +namespace Renci.SshNet.IntegrationBenchmarks { public class IntegrationBenchmarkBase { @@ -9,6 +9,7 @@ public async Task GlobalSetup() { await InfrastructureFixture.Instance.InitializeAsync().ConfigureAwait(false); } + public async Task GlobalCleanup() { await InfrastructureFixture.Instance.DisposeAsync().ConfigureAwait(false); diff --git a/test/Renci.SshNet.IntegrationBenchmarks/Program.cs b/test/Renci.SshNet.IntegrationBenchmarks/Program.cs new file mode 100644 index 000000000..58c23eb50 --- /dev/null +++ b/test/Renci.SshNet.IntegrationBenchmarks/Program.cs @@ -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); + } + } +} diff --git a/test/Renci.SshNet.IntegrationBenchmarks/Renci.SshNet.IntegrationBenchmarks.csproj b/test/Renci.SshNet.IntegrationBenchmarks/Renci.SshNet.IntegrationBenchmarks.csproj new file mode 100644 index 000000000..2af6c25ce --- /dev/null +++ b/test/Renci.SshNet.IntegrationBenchmarks/Renci.SshNet.IntegrationBenchmarks.csproj @@ -0,0 +1,25 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + diff --git a/test/Renci.SshNet.Benchmarks/IntegrationBenchmarks/Test.cs b/test/Renci.SshNet.IntegrationBenchmarks/SshClientBenchmark.cs similarity index 87% rename from test/Renci.SshNet.Benchmarks/IntegrationBenchmarks/Test.cs rename to test/Renci.SshNet.IntegrationBenchmarks/SshClientBenchmark.cs index 200530e2f..87410ed43 100644 --- a/test/Renci.SshNet.Benchmarks/IntegrationBenchmarks/Test.cs +++ b/test/Renci.SshNet.IntegrationBenchmarks/SshClientBenchmark.cs @@ -2,14 +2,19 @@ using Renci.SshNet.IntegrationTests.TestsFixtures; -namespace Renci.SshNet.Benchmarks.IntegrationBenchmarks +namespace Renci.SshNet.IntegrationBenchmarks { [MemoryDiagnoser] [SimpleJob] - public class Test : IntegrationBenchmarkBase + public class SshClientBenchmark : IntegrationBenchmarkBase { private readonly InfrastructureFixture _infrastructureFixture; + public SshClientBenchmark() + { + _infrastructureFixture = InfrastructureFixture.Instance; + } + [GlobalSetup] public async Task Setup() { @@ -22,11 +27,6 @@ public async Task Cleanup() await GlobalCleanup().ConfigureAwait(false); } - public Test() - { - _infrastructureFixture = InfrastructureFixture.Instance; - } - [Benchmark] public string Connect() { From 70670a724ea5b98b5e07a7ac59a7c6d66de6a26f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Nag=C3=B3rski?= Date: Sat, 27 Jan 2024 16:22:36 +0100 Subject: [PATCH 3/5] Write with encoding --- src/Renci.SshNet/Common/SshDataStream.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Renci.SshNet/Common/SshDataStream.cs b/src/Renci.SshNet/Common/SshDataStream.cs index 9e2a28051..0a57a622c 100644 --- a/src/Renci.SshNet/Common/SshDataStream.cs +++ b/src/Renci.SshNet/Common/SshDataStream.cs @@ -126,9 +126,17 @@ public void Write(string s, Encoding encoding) { throw new ArgumentNullException(nameof(encoding)); } - +#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER + ReadOnlySpan value = s; + var count = encoding.GetByteCount(value); + Span bytes = stackalloc byte[count]; + encoding.GetBytes(value, bytes); + Write((uint) count); + Write(bytes); +#else var bytes = encoding.GetBytes(s); WriteBinary(bytes, 0, bytes.Length); +#endif } /// From fda166cece7bb47380aba363f1668749413699f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Nag=C3=B3rski?= Date: Sat, 27 Jan 2024 22:32:18 +0100 Subject: [PATCH 4/5] Update src/Renci.SshNet/Common/SshDataStream.cs Co-authored-by: Rob Hague --- src/Renci.SshNet/Common/SshDataStream.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Renci.SshNet/Common/SshDataStream.cs b/src/Renci.SshNet/Common/SshDataStream.cs index 0a57a622c..a8571517a 100644 --- a/src/Renci.SshNet/Common/SshDataStream.cs +++ b/src/Renci.SshNet/Common/SshDataStream.cs @@ -129,7 +129,9 @@ public void Write(string s, Encoding encoding) #if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER ReadOnlySpan value = s; var count = encoding.GetByteCount(value); - Span bytes = stackalloc byte[count]; + Span bytes = count <= 256 + ? stackalloc byte[count] + : new byte[count] encoding.GetBytes(value, bytes); Write((uint) count); Write(bytes); From e6a86849171f7ff23378a9e73c90d710d91e3189 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Nag=C3=B3rski?= Date: Sun, 28 Jan 2024 10:12:42 +0100 Subject: [PATCH 5/5] Add more benchmarks --- src/Renci.SshNet/Common/SshDataStream.cs | 4 +- .../ScpClientBenchmark.cs | 79 +++++++++++++++++++ .../SftpClientBenchmark.cs | 78 ++++++++++++++++++ .../SshClientBenchmark.cs | 33 +++++++- 4 files changed, 190 insertions(+), 4 deletions(-) create mode 100644 test/Renci.SshNet.IntegrationBenchmarks/ScpClientBenchmark.cs create mode 100644 test/Renci.SshNet.IntegrationBenchmarks/SftpClientBenchmark.cs diff --git a/src/Renci.SshNet/Common/SshDataStream.cs b/src/Renci.SshNet/Common/SshDataStream.cs index a8571517a..5fb40adcf 100644 --- a/src/Renci.SshNet/Common/SshDataStream.cs +++ b/src/Renci.SshNet/Common/SshDataStream.cs @@ -129,9 +129,7 @@ public void Write(string s, Encoding encoding) #if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER ReadOnlySpan value = s; var count = encoding.GetByteCount(value); - Span bytes = count <= 256 - ? stackalloc byte[count] - : new byte[count] + var bytes = count <= 256 ? stackalloc byte[count] : new byte[count]; encoding.GetBytes(value, bytes); Write((uint) count); Write(bytes); diff --git a/test/Renci.SshNet.IntegrationBenchmarks/ScpClientBenchmark.cs b/test/Renci.SshNet.IntegrationBenchmarks/ScpClientBenchmark.cs new file mode 100644 index 000000000..1e7b9368b --- /dev/null +++ b/test/Renci.SshNet.IntegrationBenchmarks/ScpClientBenchmark.cs @@ -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()); + } + } +} diff --git a/test/Renci.SshNet.IntegrationBenchmarks/SftpClientBenchmark.cs b/test/Renci.SshNet.IntegrationBenchmarks/SftpClientBenchmark.cs new file mode 100644 index 000000000..b3d4ad17e --- /dev/null +++ b/test/Renci.SshNet.IntegrationBenchmarks/SftpClientBenchmark.cs @@ -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 ListDirectory() + { + return _sftpClient!.ListDirectory("/root"); + } + + public IAsyncEnumerable 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()); + } + } +} diff --git a/test/Renci.SshNet.IntegrationBenchmarks/SshClientBenchmark.cs b/test/Renci.SshNet.IntegrationBenchmarks/SshClientBenchmark.cs index 87410ed43..0ce126e0e 100644 --- a/test/Renci.SshNet.IntegrationBenchmarks/SshClientBenchmark.cs +++ b/test/Renci.SshNet.IntegrationBenchmarks/SshClientBenchmark.cs @@ -9,6 +9,7 @@ namespace Renci.SshNet.IntegrationBenchmarks public class SshClientBenchmark : IntegrationBenchmarkBase { private readonly InfrastructureFixture _infrastructureFixture; + private SshClient? _sshClient; public SshClientBenchmark() { @@ -19,6 +20,8 @@ public SshClientBenchmark() 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] @@ -28,11 +31,39 @@ public async Task Cleanup() } [Benchmark] - public string Connect() + 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 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; + } } }