Skip to content

Commit 0371af9

Browse files
Integration benchmark tests (#1298)
* Integration benchmark tests * Fixes * Write with encoding * Update src/Renci.SshNet/Common/SshDataStream.cs Co-authored-by: Rob Hague <[email protected]> * Add more benchmarks --------- Co-authored-by: Rob Hague <[email protected]>
1 parent 64b428f commit 0371af9

File tree

10 files changed

+386
-1
lines changed

10 files changed

+386
-1
lines changed

Renci.SshNet.sln

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "svg", "svg", "{92E7B1B8-4C7
8888
EndProject
8989
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Renci.SshNet.Benchmarks", "test\Renci.SshNet.Benchmarks\Renci.SshNet.Benchmarks.csproj", "{CF6CA77F-E4B8-4522-B267-E3F555E2E7B1}"
9090
EndProject
91+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Renci.SshNet.IntegrationBenchmarks", "test\Renci.SshNet.IntegrationBenchmarks\Renci.SshNet.IntegrationBenchmarks.csproj", "{6DFC1807-3F44-4302-A302-43F7D887C4E0}"
92+
EndProject
9193
Global
9294
GlobalSection(SolutionConfigurationPlatforms) = preSolution
9395
Debug|Any CPU = Debug|Any CPU
@@ -196,6 +198,26 @@ Global
196198
{CF6CA77F-E4B8-4522-B267-E3F555E2E7B1}.Release|x64.Build.0 = Release|Any CPU
197199
{CF6CA77F-E4B8-4522-B267-E3F555E2E7B1}.Release|x86.ActiveCfg = Release|Any CPU
198200
{CF6CA77F-E4B8-4522-B267-E3F555E2E7B1}.Release|x86.Build.0 = Release|Any CPU
201+
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
202+
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
203+
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Debug|ARM.ActiveCfg = Debug|Any CPU
204+
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Debug|ARM.Build.0 = Debug|Any CPU
205+
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
206+
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
207+
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Debug|x64.ActiveCfg = Debug|Any CPU
208+
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Debug|x64.Build.0 = Debug|Any CPU
209+
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Debug|x86.ActiveCfg = Debug|Any CPU
210+
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Debug|x86.Build.0 = Debug|Any CPU
211+
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
212+
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Release|Any CPU.Build.0 = Release|Any CPU
213+
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Release|ARM.ActiveCfg = Release|Any CPU
214+
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Release|ARM.Build.0 = Release|Any CPU
215+
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
216+
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Release|Mixed Platforms.Build.0 = Release|Any CPU
217+
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Release|x64.ActiveCfg = Release|Any CPU
218+
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Release|x64.Build.0 = Release|Any CPU
219+
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Release|x86.ActiveCfg = Release|Any CPU
220+
{6DFC1807-3F44-4302-A302-43F7D887C4E0}.Release|x86.Build.0 = Release|Any CPU
199221
EndGlobalSection
200222
GlobalSection(SolutionProperties) = preSolution
201223
HideSolutionNode = FALSE

src/Renci.SshNet/Common/SshDataStream.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,17 @@ public void Write(string s, Encoding encoding)
126126
{
127127
throw new ArgumentNullException(nameof(encoding));
128128
}
129-
129+
#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER
130+
ReadOnlySpan<char> value = s;
131+
var count = encoding.GetByteCount(value);
132+
var bytes = count <= 256 ? stackalloc byte[count] : new byte[count];
133+
encoding.GetBytes(value, bytes);
134+
Write((uint) count);
135+
Write(bytes);
136+
#else
130137
var bytes = encoding.GetBytes(s);
131138
WriteBinary(bytes, 0, bytes.Length);
139+
#endif
132140
}
133141

134142
/// <summary>

test/Renci.SshNet.Benchmarks/Renci.SshNet.Benchmarks.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@
1717
<ItemGroup>
1818
<EmbeddedResource Include="..\Data\*" LinkBase="Data" />
1919
</ItemGroup>
20+
2021
</Project>
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
[*.cs]
2+
3+
#### Sonar rules ####
4+
5+
# S125: Sections of code should not be commented out
6+
https://rules.sonarsource.com/csharp/RSPEC-125/
7+
dotnet_diagnostic.S125.severity = suggestion
8+
9+
# S1118: Utility classes should not have public constructors
10+
# https://rules.sonarsource.com/csharp/RSPEC-1118/
11+
dotnet_diagnostic.S1118.severity = suggestion
12+
13+
# S1450: Private fields only used as local variables in methods should become local variables
14+
# https://rules.sonarsource.com/csharp/RSPEC-1450/
15+
dotnet_diagnostic.S1450.severity = suggestion
16+
17+
# S4144: Methods should not have identical implementations
18+
# https://rules.sonarsource.com/csharp/RSPEC-4144/
19+
dotnet_diagnostic.S4144.severity = suggestion
20+
21+
#### SYSLIB diagnostics ####
22+
23+
24+
#### StyleCop Analyzers rules ####
25+
26+
# SA1028: Code must not contain trailing whitespace
27+
# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1028.md
28+
dotnet_diagnostic.SA1028.severity = suggestion
29+
30+
# SA1414: Tuple types in signatures should have element names
31+
https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1414.md
32+
dotnet_diagnostic.SA1414.severity = suggestion
33+
34+
# SA1400: Access modifier must be declared
35+
# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1400.md
36+
dotnet_diagnostic.SA1400.severity = suggestion
37+
38+
# SA1401: Fields must be private
39+
# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1401.md
40+
dotnet_diagnostic.SA1401.severity = suggestion
41+
42+
# SA1411: Attribute constructor must not use unnecessary parenthesis
43+
# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1411.md
44+
dotnet_diagnostic.SA1411.severity = suggestion
45+
46+
# SA1505: Opening braces must not be followed by blank line
47+
# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1505.md
48+
dotnet_diagnostic.SA1505.severity = suggestion
49+
50+
# SA1512: Single line comments must not be followed by blank line
51+
# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1512.md
52+
dotnet_diagnostic.SA1512.severity = suggestion
53+
54+
# SA1513: Closing brace must be followed by blank line
55+
# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md
56+
dotnet_diagnostic.SA1513.severity = suggestion
57+
58+
#### Meziantou.Analyzer rules ####
59+
60+
# MA0003: Add parameter name to improve readability
61+
# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0003.md
62+
dotnet_diagnostic.MA0003.severity = suggestion
63+
64+
# MA0053: Make class sealed
65+
# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0053.md
66+
dotnet_diagnostic.MA0053.severity = suggestion
67+
68+
#### .NET Compiler Platform analysers rules ####
69+
70+
# CA2000: Dispose objects before losing scope
71+
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2000
72+
dotnet_diagnostic.CA2000.severity = suggestion
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using Renci.SshNet.IntegrationTests.TestsFixtures;
2+
3+
namespace Renci.SshNet.IntegrationBenchmarks
4+
{
5+
public class IntegrationBenchmarkBase
6+
{
7+
#pragma warning disable CA1822 // Mark members as static
8+
public async Task GlobalSetup()
9+
{
10+
await InfrastructureFixture.Instance.InitializeAsync().ConfigureAwait(false);
11+
}
12+
13+
public async Task GlobalCleanup()
14+
{
15+
await InfrastructureFixture.Instance.DisposeAsync().ConfigureAwait(false);
16+
}
17+
#pragma warning restore CA1822 // Mark members as static
18+
}
19+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using BenchmarkDotNet.Running;
2+
3+
namespace Renci.SshNet.IntegrationBenchmarks
4+
{
5+
public static class Program
6+
{
7+
public static void Main(string[] args)
8+
{
9+
_ = BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
10+
}
11+
}
12+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="BenchmarkDotNet" Version="0.13.9" />
12+
<!-- <PackageReference Include="SSH.NET" Version="2023.0.0" /> -->
13+
<PackageReference Include="SSH.NET" Version="2023.0.0" ExcludeAssets="All" />
14+
</ItemGroup>
15+
16+
<ItemGroup>
17+
<ProjectReference Include="..\..\src\Renci.SshNet\Renci.SshNet.csproj" />
18+
<ProjectReference Include="..\Renci.SshNet.IntegrationTests\Renci.SshNet.IntegrationTests.csproj" />
19+
</ItemGroup>
20+
21+
<ItemGroup>
22+
<EmbeddedResource Include="..\Data\*" LinkBase="Data" />
23+
</ItemGroup>
24+
25+
</Project>
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
using System.Text;
2+
using BenchmarkDotNet.Attributes;
3+
4+
using Renci.SshNet.IntegrationTests.TestsFixtures;
5+
6+
namespace Renci.SshNet.IntegrationBenchmarks
7+
{
8+
[MemoryDiagnoser]
9+
[SimpleJob]
10+
public class ScpClientBenchmark : IntegrationBenchmarkBase
11+
{
12+
private readonly InfrastructureFixture _infrastructureFixture;
13+
14+
private readonly string _file = $"/tmp/{Guid.NewGuid()}.txt";
15+
private ScpClient? _scpClient;
16+
private MemoryStream? _uploadStream;
17+
18+
public ScpClientBenchmark()
19+
{
20+
_infrastructureFixture = InfrastructureFixture.Instance;
21+
}
22+
23+
[GlobalSetup]
24+
public async Task Setup()
25+
{
26+
await GlobalSetup().ConfigureAwait(false);
27+
_scpClient = new ScpClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password);
28+
await _scpClient.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
29+
30+
var fileContent = "File content !@#$%^&*()_+{}:,./<>[];'\\|";
31+
_uploadStream = new MemoryStream(Encoding.UTF8.GetBytes(fileContent));
32+
}
33+
34+
[GlobalCleanup]
35+
public async Task Cleanup()
36+
{
37+
await GlobalCleanup().ConfigureAwait(false);
38+
await _uploadStream!.DisposeAsync().ConfigureAwait(false);
39+
}
40+
41+
[Benchmark]
42+
public void Connect()
43+
{
44+
using var scpClient = new ScpClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password);
45+
scpClient.Connect();
46+
}
47+
48+
[Benchmark]
49+
public async Task ConnectAsync()
50+
{
51+
using var scpClient = new ScpClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password);
52+
await scpClient.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
53+
}
54+
55+
[Benchmark]
56+
public string ConnectUploadAndDownload()
57+
{
58+
using var scpClient = new ScpClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password);
59+
scpClient.Connect();
60+
_uploadStream!.Position = 0;
61+
scpClient.Upload(_uploadStream, _file);
62+
using var downloadStream = new MemoryStream();
63+
scpClient.Download(_file, downloadStream);
64+
65+
return Encoding.UTF8.GetString(downloadStream.ToArray());
66+
}
67+
68+
[Benchmark]
69+
public string UploadAndDownload()
70+
{
71+
_uploadStream!.Position = 0;
72+
_scpClient!.Upload(_uploadStream, _file);
73+
using var downloadStream = new MemoryStream();
74+
_scpClient.Download(_file, downloadStream);
75+
76+
return Encoding.UTF8.GetString(downloadStream.ToArray());
77+
}
78+
}
79+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
using System.Text;
2+
3+
using BenchmarkDotNet.Attributes;
4+
5+
using Renci.SshNet.IntegrationTests.TestsFixtures;
6+
using Renci.SshNet.Sftp;
7+
8+
namespace Renci.SshNet.IntegrationBenchmarks
9+
{
10+
[MemoryDiagnoser]
11+
[SimpleJob]
12+
public class SftpClientBenchmark : IntegrationBenchmarkBase
13+
{
14+
private readonly InfrastructureFixture _infrastructureFixture;
15+
private readonly string _file = $"/tmp/{Guid.NewGuid()}.txt";
16+
17+
private SftpClient? _sftpClient;
18+
private MemoryStream? _uploadStream;
19+
20+
public SftpClientBenchmark()
21+
{
22+
_infrastructureFixture = InfrastructureFixture.Instance;
23+
}
24+
25+
[GlobalSetup]
26+
public async Task Setup()
27+
{
28+
await GlobalSetup().ConfigureAwait(false);
29+
_sftpClient = new SftpClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password);
30+
await _sftpClient.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
31+
32+
var fileContent = "File content !@#$%^&*()_+{}:,./<>[];'\\|";
33+
_uploadStream = new MemoryStream(Encoding.UTF8.GetBytes(fileContent));
34+
}
35+
36+
[GlobalCleanup]
37+
public async Task Cleanup()
38+
{
39+
await GlobalCleanup().ConfigureAwait(false);
40+
await _uploadStream!.DisposeAsync().ConfigureAwait(false);
41+
}
42+
43+
[Benchmark]
44+
public void Connect()
45+
{
46+
using var sftpClient = new SftpClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password);
47+
sftpClient.Connect();
48+
}
49+
50+
[Benchmark]
51+
public async Task ConnectAsync()
52+
{
53+
using var sftpClient = new SftpClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password);
54+
await sftpClient.ConnectAsync(CancellationToken.None).ConfigureAwait(false);
55+
}
56+
57+
public IEnumerable<ISftpFile> ListDirectory()
58+
{
59+
return _sftpClient!.ListDirectory("/root");
60+
}
61+
62+
public IAsyncEnumerable<ISftpFile> ListDirectoryAsync()
63+
{
64+
return _sftpClient!.ListDirectoryAsync("/root", CancellationToken.None);
65+
}
66+
67+
[Benchmark]
68+
public string UploadAndDownload()
69+
{
70+
_uploadStream!.Position = 0;
71+
_sftpClient!.UploadFile(_uploadStream, _file);
72+
using var downloadStream = new MemoryStream();
73+
_sftpClient.DownloadFile(_file, downloadStream);
74+
75+
return Encoding.UTF8.GetString(downloadStream.ToArray());
76+
}
77+
}
78+
}

0 commit comments

Comments
 (0)