├── ThroughputTest
├── app.config
├── Properties
│ ├── launchSettings.json
│ └── PublishProfiles
│ │ └── FolderProfile.pubxml
├── EntityType.cs
├── ThroughputTest.csproj
├── Experiment.cs
├── ThroughputTest.sln
├── MetricsData.cs
├── PerformanceTask.cs
├── DynamicSemaphoreSlim.cs
├── IncreaseInflightSendsExperiment.cs
├── IncreaseInflightReceivesExperiment.cs
├── Program.cs
├── Observable.cs
├── Metrics.cs
├── TaskEx.cs
├── Settings.cs
├── PerformanceApp.cs
├── SenderTask.cs
├── README.md
└── ReceiverTask.cs
├── ThroughputTest_v2
├── ServiceBusThroughputTest
│ ├── App.config
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── MetricsData.cs
│ ├── ServiceBusThroughputTest.sln
│ ├── ServiceBusThroughputTest.csproj
│ ├── Metrics.cs
│ └── Program.cs
├── ServiceBusThroughputTest.Common
│ ├── ILogger.cs
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ └── ServiceBusThroughputTest.Common.csproj
├── ServiceBusThroughputTestLib
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── Extensions.cs
│ ├── ServiceBusThroughputTestLib.csproj
│ ├── Sender.cs
│ └── Receiver.cs
└── README.md
├── service-bus-dotnet-msg.yml
├── README.md
├── CONTRIBUTING.md
├── LICENSE
├── .gitattributes
├── .gitignore
└── Results.txt
/ThroughputTest/app.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/ThroughputTest/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "ServiceBusPerfSample": {
4 | "commandName": "Project",
5 | "commandLineArgs": ""
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/ThroughputTest_v2/ServiceBusThroughputTest/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/service-bus-dotnet-msg.yml:
--------------------------------------------------------------------------------
1 | ### YamlMime:Sample
2 | sample:
3 | - name: Service Bus Premium Messaging .NET Performance Test
4 | description: The sample in this repo can be used to help benchmark Service Bus Premium Messaging throughput, and can be used to study performance best practices.
5 | generateZip: true
6 | author: celemensv
7 | readme: readme.md
8 | languages:
9 | - csharp
10 | technologies:
11 | - service-bus
12 | - service-bus-messaging
13 | - azure
14 |
--------------------------------------------------------------------------------
/ThroughputTest/EntityType.cs:
--------------------------------------------------------------------------------
1 | //---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
5 | // EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
6 | // OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
7 | //---------------------------------------------------------------------------------
8 |
9 | namespace ThroughputTest
10 | {
11 | enum EntityType
12 | {
13 | Queue,
14 | Topic
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/ThroughputTest/Properties/PublishProfiles/FolderProfile.pubxml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 | FileSystem
9 | Release
10 | netcoreapp2.0
11 | bin\Release\PublishOutput
12 | win-x64
13 |
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Service Bus Premium Messaging .NET Performance Test
2 |
3 | [The sample](./ThroughputTest) in this repo can be used to help benchmark Service Bus Premium Messaging throughput,
4 | and can be used to study performance best practices.
5 |
6 | A latency-focused sample will be published in the near future, as measuring latency and throughput limits at the same time is not possible. Send operations are generally somewhat less expensive than receives, and therefore 10000 sends in a fast-as-possible burst create a prompt traffic jam in the queue that a receiver simply can’t keep up with. That means the end-to-end passthrough latency for each message goes up when the throughput limits are pushed. Optimal latency, meaning a minimal passthrough time of messages through the system, is not achievable under maximum throughput pressure.
7 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Azure samples
2 |
3 | Thank you for your interest in contributing to Azure samples!
4 |
5 | ## Ways to contribute
6 |
7 | You can contribute to [Azure samples](https://azure.microsoft.com/documentation/samples/) in a few different ways:
8 |
9 | - Submit feedback on [this sample page](https://azure.microsoft.com/documentation/samples/service-bus-dotnet-messaging-performance/) whether it was helpful or not.
10 | - Submit issues through [issue tracker](https://github.com/Azure-Samples/service-bus-dotnet-messaging-performance/issues) on GitHub. We are actively monitoring the issues and improving our samples.
11 | - If you wish to make code changes to samples, or contribute something new, please follow the [GitHub Forks / Pull requests model](https://help.github.com/articles/fork-a-repo/): Fork the sample repo, make the change and propose it back by submitting a pull request.
--------------------------------------------------------------------------------
/ThroughputTest/ThroughputTest.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.0;netstandard2.0;net472
6 | win-x64;linux-x64
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/ThroughputTest/Experiment.cs:
--------------------------------------------------------------------------------
1 | //---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
5 | // EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
6 | // OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
7 | //---------------------------------------------------------------------------------
8 |
9 | namespace ThroughputTest
10 | {
11 | using System.Threading.Tasks;
12 |
13 | abstract class Experiment
14 | {
15 | public Experiment(Metrics metrics, Settings settings)
16 | {
17 | Metrics = metrics;
18 | Settings = settings;
19 | }
20 |
21 | public Metrics Metrics { get; }
22 | public Settings Settings { get; }
23 |
24 | public abstract Task Run();
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Microsoft Corporation
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/ThroughputTest/ThroughputTest.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27130.2027
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThroughputTest", "ThroughputTest.csproj", "{DEF227A5-2189-4781-B2A2-DDA2A4DDB950}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {DEF227A5-2189-4781-B2A2-DDA2A4DDB950}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {DEF227A5-2189-4781-B2A2-DDA2A4DDB950}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {DEF227A5-2189-4781-B2A2-DDA2A4DDB950}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {DEF227A5-2189-4781-B2A2-DDA2A4DDB950}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {6F83BE47-2957-4C46-9340-54180160B5A7}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/ThroughputTest_v2/ServiceBusThroughputTest.Common/ILogger.cs:
--------------------------------------------------------------------------------
1 | //---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
5 | // EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
6 | // OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
7 | //---------------------------------------------------------------------------------
8 |
9 | namespace ServiceBusThroughputTest.Common
10 | {
11 | using System;
12 |
13 | public interface ILogger : IDisposable
14 | {
15 | Guid StartRecordTime();
16 | void StartCompletionRecordTime(Guid sessionId);
17 | double StopCompletionRecordTimeAndGetElapsedMs(Guid sessionId);
18 | double StopRecordTimeAndGetElapsedMs(Guid sessionId);
19 | void IncrementActionCount(Guid sessionId);
20 | void IncrementMetricValueBy(Guid sessionId, int count);
21 | void IncrementErrorCount(Guid sessionId);
22 | void IncrementBusyErrorCount(Guid sessionId);
23 | void DisposeSession(Guid sessionId);
24 | void AddTrace(string trace);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/ThroughputTest_v2/ServiceBusThroughputTest/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("ServiceBusThroughputTest")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("ServiceBusThroughputTest")]
13 | [assembly: AssemblyCopyright("Copyright © 2023")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("72a4a64b-eadb-4a0d-8541-6893895adfad")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/ThroughputTest_v2/ServiceBusThroughputTestLib/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("ServiceBusThroughputTestLib")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("ServiceBusThroughputTestLib")]
13 | [assembly: AssemblyCopyright("Copyright © 2023")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("da73050d-b46d-4760-aef5-cd86e25dfab5")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/ThroughputTest_v2/ServiceBusThroughputTest.Common/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("ServiceBusThroughputTest.Common")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("ServiceBusThroughputTest.Common")]
13 | [assembly: AssemblyCopyright("Copyright © 2023")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("5d8a1d8a-c5b5-4fbc-b10e-1265a4a4ecbf")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/ThroughputTest_v2/ServiceBusThroughputTest/MetricsData.cs:
--------------------------------------------------------------------------------
1 | //---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
5 | // EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
6 | // OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
7 | //---------------------------------------------------------------------------------
8 |
9 | namespace ServiceBusThroughputTest
10 | {
11 | public sealed class SendMetrics
12 | {
13 | public long Tick { get; set; }
14 | public int Messages { get; set; }
15 | public int Sends { get; set; }
16 | public int Size { get; set; }
17 | public long SendDuration100ns { get; set; }
18 | public int Errors { get; set; }
19 | public int BusyErrors { get; set; }
20 | public int InflightSends { get; internal set; }
21 | }
22 |
23 | public sealed class ReceiveMetrics
24 | {
25 | public long Tick { get; set; }
26 | public int Messages { get; set; }
27 | public int Receives { get; set; }
28 | public int Size { get; set; }
29 | public long ReceiveDuration100ns { get; set; }
30 | public long CompletionStartTick { get; set; }
31 | public long CompleteDuration100ns { get; set; }
32 | public int Errors { get; set; }
33 | public int BusyErrors { get; set; }
34 | public int Completions { get; internal set; }
35 | public int CompleteCalls { get; internal set; }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/ThroughputTest/MetricsData.cs:
--------------------------------------------------------------------------------
1 | //---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
5 | // EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
6 | // OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
7 | //---------------------------------------------------------------------------------
8 |
9 | namespace ThroughputTest
10 | {
11 | sealed class SendMetrics
12 | {
13 | public long Tick { get; set; }
14 | public int Messages { get; set; }
15 | public int Sends { get; set; }
16 | public int Size { get; set; }
17 | public long SendDuration100ns { get; set; }
18 | public long GateLockDuration100ns { get; set; }
19 | public int Errors { get; set; }
20 | public int BusyErrors { get; set; }
21 | public int InflightSends { get; internal set; }
22 | }
23 |
24 | sealed class ReceiveMetrics
25 | {
26 | public long Tick { get; set; }
27 | public int Messages { get; set; }
28 | public int Receives { get; set; }
29 | public int Size { get; set; }
30 | public long ReceiveDuration100ns { get; set; }
31 | public long CompleteDuration100ns { get; set; }
32 | public long GateLockDuration100ns { get; set; }
33 | public int Errors { get; set; }
34 | public int BusyErrors { get; set; }
35 | public int Completions { get; internal set; }
36 | public int CompleteCalls { get; internal set; }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/ThroughputTest/PerformanceTask.cs:
--------------------------------------------------------------------------------
1 | //---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
5 | // EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
6 | // OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
7 | //---------------------------------------------------------------------------------
8 |
9 | namespace ThroughputTest
10 | {
11 | using System.Threading;
12 | using System.Threading.Tasks;
13 |
14 | abstract class PerformanceTask
15 | {
16 | protected PerformanceTask(Settings settings, Metrics metrics, CancellationToken cancellationToken)
17 | {
18 | this.Settings = settings;
19 | this.Metrics = metrics;
20 | this.CancellationToken = cancellationToken;
21 | }
22 |
23 | protected Settings Settings { get; private set; }
24 |
25 | protected string ConnectionString { get; private set; }
26 |
27 | protected Metrics Metrics { get; private set; }
28 |
29 | protected CancellationToken CancellationToken { get; private set; }
30 |
31 | public void Close()
32 | {
33 | this.CloseAsync().Fork();
34 | }
35 |
36 | public async Task OpenAsync()
37 | {
38 | await OnOpenAsync().ConfigureAwait(false);
39 | }
40 |
41 | public Task StartAsync()
42 | {
43 | return OnStartAsync();
44 | }
45 |
46 | public Task CloseAsync()
47 | {
48 | return Task.CompletedTask;
49 | }
50 |
51 | protected abstract Task OnOpenAsync();
52 |
53 | protected abstract Task OnStartAsync();
54 |
55 |
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/ThroughputTest_v2/ServiceBusThroughputTestLib/Extensions.cs:
--------------------------------------------------------------------------------
1 | //---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
5 | // EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
6 | // OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
7 | //---------------------------------------------------------------------------------
8 |
9 | namespace ServiceBusThroughputTestLib.Extensions
10 | {
11 | using Azure.Messaging.ServiceBus;
12 | using ServiceBusThroughputTest.Common;
13 | using System;
14 | using System.Threading;
15 |
16 | static class Extensions
17 | {
18 | public static void HandleExceptions(this AggregateException ex, ILogger logger, Guid? sessionId, CancellationToken cancellationToken)
19 | {
20 | // bool wait = false;
21 |
22 | logger?.AddTrace(ex.InnerException.ToString());
23 |
24 | ex.Handle((x) =>
25 | {
26 | if (sessionId != null)
27 | {
28 | if ((x as ServiceBusException)?.Reason == ServiceBusFailureReason.ServiceBusy)
29 | {
30 | logger?.IncrementBusyErrorCount(sessionId.Value);
31 | if (!cancellationToken.IsCancellationRequested)
32 | {
33 | // wait = true;
34 | }
35 | }
36 | else
37 | {
38 | logger?.IncrementErrorCount(sessionId.Value);
39 | }
40 | }
41 |
42 | return true;
43 | });
44 |
45 | //if (wait)
46 | //{
47 | // await Task.Delay(3000, cancellationToken).ConfigureAwait(false);
48 | //}
49 |
50 | if (sessionId != null)
51 | logger?.DisposeSession(sessionId.Value);
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/ThroughputTest_v2/ServiceBusThroughputTest/ServiceBusThroughputTest.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.33801.447
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceBusThroughputTest", "ServiceBusThroughputTest.csproj", "{72A4A64B-EADB-4A0D-8541-6893895ADFAD}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceBusThroughputTest.Common", "..\ServiceBusThroughputTest.Common\ServiceBusThroughputTest.Common.csproj", "{5D8A1D8A-C5B5-4FBC-B10E-1265A4A4ECBF}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceBusThroughputTestLib", "..\ServiceBusThroughputTestLib\ServiceBusThroughputTestLib.csproj", "{DA73050D-B46D-4760-AEF5-CD86E25DFAB5}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Release|Any CPU = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {72A4A64B-EADB-4A0D-8541-6893895ADFAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {72A4A64B-EADB-4A0D-8541-6893895ADFAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {72A4A64B-EADB-4A0D-8541-6893895ADFAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {72A4A64B-EADB-4A0D-8541-6893895ADFAD}.Release|Any CPU.Build.0 = Release|Any CPU
22 | {5D8A1D8A-C5B5-4FBC-B10E-1265A4A4ECBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {5D8A1D8A-C5B5-4FBC-B10E-1265A4A4ECBF}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {5D8A1D8A-C5B5-4FBC-B10E-1265A4A4ECBF}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {5D8A1D8A-C5B5-4FBC-B10E-1265A4A4ECBF}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {DA73050D-B46D-4760-AEF5-CD86E25DFAB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {DA73050D-B46D-4760-AEF5-CD86E25DFAB5}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {DA73050D-B46D-4760-AEF5-CD86E25DFAB5}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {DA73050D-B46D-4760-AEF5-CD86E25DFAB5}.Release|Any CPU.Build.0 = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(SolutionProperties) = preSolution
32 | HideSolutionNode = FALSE
33 | EndGlobalSection
34 | GlobalSection(ExtensibilityGlobals) = postSolution
35 | SolutionGuid = {860DD522-AB3C-42BC-97DA-33678D48995D}
36 | EndGlobalSection
37 | EndGlobal
38 |
--------------------------------------------------------------------------------
/ThroughputTest_v2/ServiceBusThroughputTest.Common/ServiceBusThroughputTest.Common.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {5D8A1D8A-C5B5-4FBC-B10E-1265A4A4ECBF}
8 | Library
9 | Properties
10 | ServiceBusThroughputTest.Common
11 | ServiceBusThroughputTest.Common
12 | v4.7.2
13 | 512
14 | true
15 |
16 |
17 | true
18 | full
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 |
25 |
26 | pdbonly
27 | true
28 | bin\Release\
29 | TRACE
30 | prompt
31 | 4
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/ThroughputTest/DynamicSemaphoreSlim.cs:
--------------------------------------------------------------------------------
1 | //---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
5 | // EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
6 | // OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
7 | //---------------------------------------------------------------------------------
8 |
9 | namespace ThroughputTest
10 | {
11 | using System;
12 | using System.Threading;
13 | using System.Threading.Tasks;
14 | public class DynamicSemaphoreSlim : SemaphoreSlim
15 | {
16 | const int overallocationCount = 10000;
17 | long remainingOverallocation;
18 | public DynamicSemaphoreSlim(int initialCount) : base(initialCount + overallocationCount)
19 | {
20 | remainingOverallocation = overallocationCount;
21 | // grab the overallocatedCounts
22 | for (int i = 0; i < overallocationCount; i++)
23 | {
24 | this.Wait();
25 | }
26 | }
27 |
28 | ///
29 | /// Grant one more count
30 | ///
31 | public void Grant()
32 | {
33 | if (Interlocked.Decrement(ref remainingOverallocation) < 0)
34 | {
35 | throw new InvalidOperationException();
36 | }
37 | this.Release();
38 | }
39 |
40 | ///
41 | /// Revoke a count once possible
42 | ///
43 | /// Task that completes when revocation is complete
44 | public Task RevokeAsync()
45 | {
46 | if (Interlocked.Increment(ref remainingOverallocation) > overallocationCount)
47 | {
48 | throw new InvalidOperationException();
49 | }
50 | return this.WaitAsync();
51 | }
52 |
53 | ///
54 | /// Revoke a count once possible (does not block)
55 | ///
56 | public void Revoke()
57 | {
58 | if (Interlocked.Increment(ref remainingOverallocation) > overallocationCount)
59 | {
60 | throw new InvalidOperationException();
61 | }
62 | this.WaitAsync().ContinueWith((t)=> { }); // don't await
63 | }
64 |
65 |
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/ThroughputTest/IncreaseInflightSendsExperiment.cs:
--------------------------------------------------------------------------------
1 | //---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
5 | // EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
6 | // OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
7 | //---------------------------------------------------------------------------------
8 |
9 | namespace ThroughputTest
10 | {
11 | using System;
12 | using System.Linq;
13 | using System.Reactive.Linq;
14 | using System.Threading.Tasks;
15 |
16 | class IncreaseInflightSendsExperiment : Experiment
17 | {
18 | private int count;
19 |
20 | public IncreaseInflightSendsExperiment(int count, Metrics metrics, Settings settings) : base(metrics, settings)
21 | {
22 | this.count = count;
23 | }
24 |
25 | public override async Task Run()
26 | {
27 | if (Settings.SenderCount == 0)
28 | {
29 | return null;
30 | }
31 |
32 | await Task.Delay(10).ConfigureAwait(false); // 10 seconds to meter
33 | var firstObservation = await ((IObservable)Metrics).Buffer(TimeSpan.FromSeconds(5)).FirstAsync();
34 | var firstMessageCount = firstObservation.Sum(i => i.Messages);
35 | if ((Settings.MaxInflightSends.Value + count) < 1)
36 | {
37 | Settings.MaxInflightSends.Value = 1;
38 | count = 1;
39 | }
40 | {
41 | Settings.MaxInflightSends.Value += count;
42 | }
43 | await Task.Delay(20).ConfigureAwait(false); // 20 seconds to settle
44 |
45 | var secondObservation = await ((IObservable)Metrics).Buffer(TimeSpan.FromSeconds(5)).FirstAsync();
46 | var secondMessageCount = secondObservation.Sum(i => i.Messages);
47 |
48 | if (secondMessageCount > firstMessageCount)
49 | {
50 | return new IncreaseInflightSendsExperiment(this.count, this.Metrics, this.Settings);
51 | }
52 | else
53 | {
54 | Settings.MaxInflightSends.Value -= count;
55 | return new IncreaseInflightSendsExperiment(this.count, this.Metrics, this.Settings);
56 | }
57 | }
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/ThroughputTest/IncreaseInflightReceivesExperiment.cs:
--------------------------------------------------------------------------------
1 | //---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
5 | // EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
6 | // OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
7 | //---------------------------------------------------------------------------------
8 |
9 | namespace ThroughputTest
10 | {
11 | using System;
12 | using System.Linq;
13 | using System.Reactive.Linq;
14 | using System.Threading.Tasks;
15 |
16 | class IncreaseInflightReceivesExperiment : Experiment
17 | {
18 | private int count;
19 |
20 | public IncreaseInflightReceivesExperiment(int count, Metrics metrics, Settings settings) : base(metrics, settings)
21 | {
22 | this.count = count;
23 | }
24 |
25 | public override async Task Run()
26 | {
27 | if (Settings.ReceiverCount == 0)
28 | {
29 | return null;
30 | }
31 |
32 | await Task.Delay(10).ConfigureAwait(false); // 10 seconds to meter
33 | var firstObservation = await ((IObservable)Metrics).Buffer(TimeSpan.FromSeconds(5)).FirstAsync();
34 | var firstMessageCount = firstObservation.Sum(i => i.Messages);
35 | if ((Settings.MaxInflightReceives.Value + count) < 1)
36 | {
37 | Settings.MaxInflightReceives.Value = 1;
38 | count = 1;
39 | }
40 | {
41 | Settings.MaxInflightReceives.Value += count;
42 | }
43 | await Task.Delay(20).ConfigureAwait(false); // 20 seconds to settle and meter
44 |
45 | var secondObservation = await ((IObservable)Metrics).Buffer(TimeSpan.FromSeconds(5)).FirstAsync();
46 | var secondMessageCount = secondObservation.Sum(i => i.Messages);
47 |
48 | if (secondMessageCount > firstMessageCount)
49 | {
50 | return new IncreaseInflightReceivesExperiment(this.count, this.Metrics, this.Settings);
51 | }
52 | else
53 | {
54 | Settings.MaxInflightReceives.Value -= count;
55 | return new IncreaseInflightReceivesExperiment(this.count, this.Metrics, this.Settings);
56 | }
57 | }
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/ThroughputTest/Program.cs:
--------------------------------------------------------------------------------
1 | //---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
5 | // EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
6 | // OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
7 | //---------------------------------------------------------------------------------
8 |
9 | namespace ThroughputTest
10 | {
11 | using CommandLine;
12 | using System;
13 | using System.Linq;
14 | using Azure.Messaging.ServiceBus;
15 |
16 | class Program
17 | {
18 | static int result = 0;
19 | static int Main(params string[] args)
20 | {
21 | CommandLine.Parser.Default.ParseArguments(args)
22 | .WithParsed(opts => RunOptionsAndReturnExitCode(opts));
23 | return result;
24 | }
25 |
26 | static void RunOptionsAndReturnExitCode(Settings settings)
27 | {
28 | ServiceBusConnectionStringProperties cb = ServiceBusConnectionStringProperties.Parse(settings.ConnectionString);
29 | if (string.IsNullOrWhiteSpace(cb.EntityPath))
30 | {
31 | if (string.IsNullOrWhiteSpace(settings.SendPath))
32 | {
33 | Console.WriteLine("--send-path option must be specified if there's no EntityPath in the connection string.");
34 | result = 1;
35 | return;
36 | }
37 | }
38 | else
39 | {
40 | if (string.IsNullOrWhiteSpace(settings.SendPath))
41 | {
42 | settings.SendPath = cb.EntityPath;
43 | }
44 |
45 | settings.ConnectionString = cb.ToString();
46 | }
47 | if (settings.ReceivePaths == null || settings.ReceivePaths.Count() == 0)
48 | {
49 | settings.ReceivePaths = new string[] { settings.SendPath };
50 | }
51 | Console.WriteLine("\n\nPress to STOP at anytime\n");
52 | Metrics metrics = new Metrics(settings);
53 | ServiceBusPerformanceApp app = new ServiceBusPerformanceApp(settings, metrics);
54 | var experiments = new Experiment[]
55 | {
56 | // new IncreaseInflightSendsExperiment(50, metrics, settings),
57 | // new IncreaseInflightReceivesExperiment(10, metrics, settings)
58 | };
59 | app.Run(experiments).Wait();
60 | Console.WriteLine("Complete");
61 |
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/ThroughputTest/Observable.cs:
--------------------------------------------------------------------------------
1 | //---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
5 | // EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
6 | // OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
7 | //---------------------------------------------------------------------------------
8 |
9 | namespace ThroughputTest
10 | {
11 | using System;
12 |
13 | public class Observable
14 | {
15 | public class ChangingEventArgs : EventArgs
16 | {
17 | public T OldValue { get; private set; }
18 | public T NewValue { get; private set; }
19 | public bool Cancel { get; set; }
20 |
21 | public ChangingEventArgs(T oldValue, T newValue)
22 | {
23 | OldValue = oldValue;
24 | NewValue = newValue;
25 | Cancel = false;
26 | }
27 | }
28 |
29 | public class ChangedEventArgs : EventArgs
30 | {
31 | public T Value { get; private set; }
32 |
33 | public ChangedEventArgs(T value)
34 | {
35 | Value = value;
36 | }
37 | }
38 |
39 | public event EventHandler Changing;
40 | public event EventHandler Changed;
41 |
42 | protected T value;
43 |
44 | public T Value
45 | {
46 | get
47 | {
48 | return value;
49 | }
50 | set
51 | {
52 | if (this.value.Equals(value))
53 | {
54 | return;
55 | }
56 | ChangingEventArgs e = new ChangingEventArgs(this.value, value);
57 | OnChanging(e);
58 | if (e.Cancel)
59 | {
60 | return;
61 | }
62 | this.value = value;
63 | OnChanged(new ChangedEventArgs(this.value));
64 | }
65 | }
66 |
67 | public Observable() { }
68 |
69 | public Observable(T value)
70 | {
71 | this.value = value;
72 | }
73 |
74 | protected virtual void OnChanging(ChangingEventArgs e)
75 | {
76 | EventHandler handler = Changing;
77 | if (handler == null)
78 | {
79 | return;
80 | }
81 | handler(this, e);
82 | }
83 |
84 | protected virtual void OnChanged(ChangedEventArgs e)
85 | {
86 | EventHandler handler = Changed;
87 | if (handler == null)
88 | {
89 | return;
90 | }
91 | handler(this, e);
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/ThroughputTest_v2/ServiceBusThroughputTestLib/ServiceBusThroughputTestLib.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {DA73050D-B46D-4760-AEF5-CD86E25DFAB5}
8 | Library
9 | Properties
10 | ServiceBusThroughputTestLib
11 | ServiceBusThroughputTestLib
12 | v4.7.2
13 | 512
14 | true
15 |
16 |
17 | true
18 | full
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 |
25 |
26 | pdbonly
27 | true
28 | bin\Release\
29 | TRACE
30 | prompt
31 | 4
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | 7.15.0
52 |
53 |
54 |
55 |
56 | {5d8a1d8a-c5b5-4fbc-b10e-1265a4a4ecbf}
57 | ServiceBusThroughputTest.Common
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/ThroughputTest/Metrics.cs:
--------------------------------------------------------------------------------
1 | //---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
5 | // EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
6 | // OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
7 | //---------------------------------------------------------------------------------
8 |
9 | namespace ThroughputTest
10 | {
11 | using System;
12 | using System.Collections.Generic;
13 | using System.Threading.Tasks;
14 |
15 | sealed class Metrics : IObservable, IObservable
16 | {
17 | object sendMetricsObserverLock = new object();
18 | List> sendMetricsObservers = new List>();
19 | object receiveMetricsObserverLock = new object();
20 | List> receiveMetricsObservers = new List>();
21 |
22 | public Metrics(Settings settings)
23 | {
24 | }
25 |
26 | public void PushSendMetrics(SendMetrics sendMetrics)
27 | {
28 | Task.Run(() =>
29 | {
30 | IObserver[] observers;
31 | lock (sendMetricsObserverLock)
32 | {
33 | observers = sendMetricsObservers.ToArray();
34 | }
35 |
36 | foreach (var observer in observers)
37 | {
38 | observer.OnNext(sendMetrics);
39 | }
40 | }).Fork();
41 | }
42 |
43 | public void PushReceiveMetrics(ReceiveMetrics receiveMetrics)
44 | {
45 | Task.Run(() =>
46 | {
47 | IObserver[] observers;
48 | lock (receiveMetricsObserverLock)
49 | {
50 | observers = receiveMetricsObservers.ToArray();
51 | }
52 |
53 | foreach (var observer in observers)
54 | {
55 | observer.OnNext(receiveMetrics);
56 | }
57 | }).Fork();
58 | }
59 |
60 | public IDisposable Subscribe(IObserver observer)
61 | {
62 | lock (receiveMetricsObserverLock)
63 | {
64 | receiveMetricsObservers.Add(observer);
65 | }
66 | return System.Reactive.Disposables.Disposable.Create(() =>
67 | {
68 | lock (receiveMetricsObserverLock)
69 | {
70 | receiveMetricsObservers.Remove(observer);
71 | }
72 | });
73 | }
74 |
75 | public IDisposable Subscribe(IObserver observer)
76 | {
77 | lock (sendMetricsObserverLock)
78 | {
79 | sendMetricsObservers.Add(observer);
80 | }
81 | return System.Reactive.Disposables.Disposable.Create(() =>
82 | {
83 | lock (sendMetricsObserverLock)
84 | {
85 | sendMetricsObservers.Remove(observer);
86 | }
87 | });
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/ThroughputTest/TaskEx.cs:
--------------------------------------------------------------------------------
1 | //---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
5 | // EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
6 | // OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
7 | //---------------------------------------------------------------------------------
8 |
9 | namespace ThroughputTest
10 | {
11 | using System;
12 | using System.Collections.Generic;
13 | using System.Threading;
14 | using System.Threading.Tasks;
15 |
16 | static class TaskEx
17 | {
18 | public static Task ParallelForEachAsync(this IEnumerable source, Func action)
19 | {
20 | List tasks = new List();
21 | foreach (TSource i in source)
22 | {
23 | tasks.Add(action(i));
24 | }
25 |
26 | return Task.WhenAll(tasks.ToArray());
27 | }
28 |
29 | public static Task ParallelForEachAsync(this IEnumerable source, Func action)
30 | {
31 | List tasks = new List();
32 |
33 | long index = 0;
34 | foreach (TSource i in source)
35 | {
36 | tasks.Add(action(i, index));
37 | index++;
38 | }
39 |
40 | return Task.WhenAll(tasks.ToArray());
41 | }
42 |
43 | public static void ForEach(this IEnumerable source, Action action)
44 | {
45 | long index = 0;
46 | foreach (TSource i in source)
47 | {
48 | action(i, index);
49 | index++;
50 | }
51 | }
52 |
53 | public static void Fork(this Task thisTask)
54 | {
55 | thisTask.ContinueWith(t => { });
56 | }
57 |
58 | public static void For(long start, long end, Action action)
59 | {
60 | for (long i = start; i < end; i++)
61 | {
62 | action(i);
63 | }
64 | }
65 |
66 | public static async Task ParallelForAsync(long start, long end, Func action)
67 | {
68 | List tasks = new List();
69 |
70 | for (long i = start; i < end; i++)
71 | {
72 | tasks.Add(action(i));
73 | }
74 |
75 | await Task.WhenAll(tasks.ToArray()).ConfigureAwait(false);
76 | }
77 |
78 | public static async Task Delay(TimeSpan delay, CancellationToken cancellationToken)
79 | {
80 | try
81 | {
82 | await Task.Delay(delay, cancellationToken).ConfigureAwait(false);
83 | }
84 | catch (TaskCanceledException)
85 | {
86 |
87 | }
88 | }
89 |
90 | public static async Task IgnoreExceptionAsync(Func task)
91 | {
92 | try
93 | {
94 | await task().ConfigureAwait(false);
95 | }
96 | catch
97 | {
98 |
99 | }
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/ThroughputTest_v2/ServiceBusThroughputTest/ServiceBusThroughputTest.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {72A4A64B-EADB-4A0D-8541-6893895ADFAD}
8 | Exe
9 | ServiceBusThroughputTest
10 | ServiceBusThroughputTest
11 | v4.7.2
12 | 512
13 | true
14 | true
15 |
16 |
17 | AnyCPU
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 |
26 |
27 | AnyCPU
28 | pdbonly
29 | true
30 | bin\Release\
31 | TRACE
32 | prompt
33 | 4
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | 2.9.1
58 |
59 |
60 | 13.0.3
61 |
62 |
63 | 4.5.1
64 |
65 |
66 |
67 |
68 | {5d8a1d8a-c5b5-4fbc-b10e-1265a4a4ecbf}
69 | ServiceBusThroughputTest.Common
70 |
71 |
72 | {da73050d-b46d-4760-aef5-cd86e25dfab5}
73 | ServiceBusThroughputTestLib
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | [Xx]64/
19 | [Xx]86/
20 | [Bb]uild/
21 | bld/
22 | [Bb]in/
23 | [Oo]bj/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | artifacts/
46 |
47 | *_i.c
48 | *_p.c
49 | *_i.h
50 | *.ilk
51 | *.meta
52 | *.obj
53 | *.pch
54 | *.pdb
55 | *.pgc
56 | *.pgd
57 | *.rsp
58 | *.sbr
59 | *.tlb
60 | *.tli
61 | *.tlh
62 | *.tmp
63 | *.tmp_proj
64 | *.log
65 | *.vspscc
66 | *.vssscc
67 | .builds
68 | *.pidb
69 | *.svclog
70 | *.scc
71 |
72 | # Chutzpah Test files
73 | _Chutzpah*
74 |
75 | # Visual C++ cache files
76 | ipch/
77 | *.aps
78 | *.ncb
79 | *.opendb
80 | *.opensdf
81 | *.sdf
82 | *.cachefile
83 | *.VC.db
84 |
85 | # Visual Studio profiler
86 | *.psess
87 | *.vsp
88 | *.vspx
89 | *.sap
90 |
91 | # TFS 2012 Local Workspace
92 | $tf/
93 |
94 | # Guidance Automation Toolkit
95 | *.gpState
96 |
97 | # ReSharper is a .NET coding add-in
98 | _ReSharper*/
99 | *.[Rr]e[Ss]harper
100 | *.DotSettings.user
101 |
102 | # JustCode is a .NET coding add-in
103 | .JustCode
104 |
105 | # TeamCity is a build add-in
106 | _TeamCity*
107 |
108 | # DotCover is a Code Coverage Tool
109 | *.dotCover
110 |
111 | # NCrunch
112 | _NCrunch_*
113 | .*crunch*.local.xml
114 | nCrunchTemp_*
115 |
116 | # MightyMoose
117 | *.mm.*
118 | AutoTest.Net/
119 |
120 | # Web workbench (sass)
121 | .sass-cache/
122 |
123 | # Installshield output folder
124 | [Ee]xpress/
125 |
126 | # DocProject is a documentation generator add-in
127 | DocProject/buildhelp/
128 | DocProject/Help/*.HxT
129 | DocProject/Help/*.HxC
130 | DocProject/Help/*.hhc
131 | DocProject/Help/*.hhk
132 | DocProject/Help/*.hhp
133 | DocProject/Help/Html2
134 | DocProject/Help/html
135 |
136 | # Click-Once directory
137 | publish/
138 |
139 | # Publish Web Output
140 | *.[Pp]ublish.xml
141 | *.azurePubxml
142 |
143 | # TODO: Un-comment the next line if you do not want to checkin
144 | # your web deploy settings because they may include unencrypted
145 | # passwords
146 | #*.pubxml
147 | *.publishproj
148 |
149 | # NuGet Packages
150 | *.nupkg
151 | # The packages folder can be ignored because of Package Restore
152 | **/packages/*
153 | # except build/, which is used as an MSBuild target.
154 | !**/packages/build/
155 | # Uncomment if necessary however generally it will be regenerated when needed
156 | #!**/packages/repositories.config
157 | # NuGet v3's project.json files produces more ignoreable files
158 | *.nuget.props
159 | *.nuget.targets
160 |
161 | # Microsoft Azure Build Output
162 | csx/
163 | *.build.csdef
164 |
165 | # Microsoft Azure Emulator
166 | ecf/
167 | rcf/
168 |
169 | # Microsoft Azure ApplicationInsights config file
170 | ApplicationInsights.config
171 |
172 | # Windows Store app package directory
173 | AppPackages/
174 | BundleArtifacts/
175 |
176 | # Visual Studio cache files
177 | # files ending in .cache can be ignored
178 | *.[Cc]ache
179 | # but keep track of directories ending in .cache
180 | !*.[Cc]ache/
181 |
182 | # Others
183 | ClientBin/
184 | [Ss]tyle[Cc]op.*
185 | ~$*
186 | *~
187 | *.dbmdl
188 | *.dbproj.schemaview
189 | *.pfx
190 | *.publishsettings
191 | node_modules/
192 | orleans.codegen.cs
193 |
194 | # RIA/Silverlight projects
195 | Generated_Code/
196 |
197 | # Backup & report files from converting an old project file
198 | # to a newer Visual Studio version. Backup files are not needed,
199 | # because we have git ;-)
200 | _UpgradeReport_Files/
201 | Backup*/
202 | UpgradeLog*.XML
203 | UpgradeLog*.htm
204 |
205 | # SQL Server files
206 | *.mdf
207 | *.ldf
208 |
209 | # Business Intelligence projects
210 | *.rdl.data
211 | *.bim.layout
212 | *.bim_*.settings
213 |
214 | # Microsoft Fakes
215 | FakesAssemblies/
216 |
217 | # GhostDoc plugin setting file
218 | *.GhostDoc.xml
219 |
220 | # Node.js Tools for Visual Studio
221 | .ntvs_analysis.dat
222 |
223 | # Visual Studio 6 build log
224 | *.plg
225 |
226 | # Visual Studio 6 workspace options file
227 | *.opt
228 |
229 | # Visual Studio LightSwitch build output
230 | **/*.HTMLClient/GeneratedArtifacts
231 | **/*.DesktopClient/GeneratedArtifacts
232 | **/*.DesktopClient/ModelManifest.xml
233 | **/*.Server/GeneratedArtifacts
234 | **/*.Server/ModelManifest.xml
235 | _Pvt_Extensions
236 |
237 | # LightSwitch generated files
238 | GeneratedArtifacts/
239 | ModelManifest.xml
240 |
241 | # Paket dependency manager
242 | .paket/paket.exe
243 |
244 | # FAKE - F# Make
245 | .fake/
246 | /ServiceBusPerfSample/Properties/PublishProfiles/cvtest6 - Web Deploy.pubxml
247 | /ServiceBusPerfSample/Properties/Settings.settings
248 | /ServiceBusPerfSample/Properties/webjob-publish-settings.json
249 |
--------------------------------------------------------------------------------
/ThroughputTest/Settings.cs:
--------------------------------------------------------------------------------
1 | //---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
5 | // EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
6 | // OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
7 | //---------------------------------------------------------------------------------
8 |
9 | namespace ThroughputTest
10 | {
11 | using System;
12 | using System.Collections.Generic;
13 | using CommandLine;
14 | using CommandLine.Text;
15 | using Azure.Messaging.ServiceBus;
16 |
17 | class Settings
18 | {
19 | [Option('C', "connection-string", Required = true, HelpText = "Connection string")]
20 | public string ConnectionString { get; set; }
21 |
22 | [Option('S', "send-path", Required = false, HelpText = "Send path. Queue or topic name, unless set in connection string EntityPath.")]
23 | public string SendPath { get; set; }
24 |
25 | [Option('R', "receive-paths", Required = false, HelpText = "Receive paths. Mandatory for receiving from topic subscriptions. Must be {topic}/subscriptions/{subscription-name} or {queue-name}")]
26 | public IEnumerable ReceivePaths { get; set; }
27 |
28 | [Option('n', "number-of-messages", Required = false, HelpText = "Number of messages to send (default 1000000)")]
29 | public long MessageCount { get; set; } = 1000000;
30 |
31 | [Option('b', "message-size-bytes", Required = false, HelpText = "Bytes per message (default 1024)")]
32 | public int MessageSizeInBytes { get; set; } = 1024;
33 |
34 | [Option('f', "frequency-metrics", Required = false, HelpText = "Frequency of metrics display (seconds, default 10s)")]
35 | public int MetricsDisplayFrequency { get; set; } = 10;
36 |
37 | [Option('m', "receive-mode", Required = false, HelpText = "Receive mode.'PeekLock' (default) or 'ReceiveAndDelete'")]
38 | public ServiceBusReceiveMode ReceiveMode { get; set; } = ServiceBusReceiveMode.PeekLock;
39 |
40 | [Option('r', "receiver-count", Required = false, HelpText = "Number of concurrent receivers (default 1)")]
41 | public int ReceiverCount { get; set; } = 5;
42 |
43 | [Option('e', "prefetch-count", Required = false, HelpText = "Prefetch count (default 0)")]
44 | public int PrefetchCount { get; set; } = 100;
45 |
46 | [Option('t', "send-batch-count", Required = false, HelpText = "Number of messages per batch (default 0, no batching)")]
47 | public int SendBatchCount { get; set; } = 0;
48 |
49 | [Option('s', "sender-count", Required = false, HelpText = "Number of concurrent senders (default 1)")]
50 | public int SenderCount { get; set; } = 1;
51 |
52 | [Option('d', "send-delay", Required = false, HelpText = "Delay between sends of any sender (milliseconds, default 0)")]
53 | public int SendDelay { get; private set; } = 0;
54 |
55 | [Option('i', "inflight-sends", Required = false, HelpText = "Maximum numbers of concurrent in-flight send operations (default 1)")]
56 | public int CfgMaxInflightSends { get { return MaxInflightSends.Value; } set { MaxInflightSends = new Observable(value); } }
57 |
58 | public Observable MaxInflightSends { get; internal set; } = new Observable(1);
59 |
60 | [Option('j', "inflight-receives", Required = false, HelpText = "Maximum number of concurrent in-flight receive operations per receiver (default 1)")]
61 | public int CfgMaxInflightReceives { get { return MaxInflightReceives.Value; } set { MaxInflightReceives = new Observable(value); } }
62 | public Observable MaxInflightReceives { get; internal set; } = new Observable(1);
63 |
64 | [Option('v', "receive-batch-count", Required = false, HelpText = "Max number of messages per batch (default 0, no batching)")]
65 | public int ReceiveBatchCount { get; private set; } = 0;
66 |
67 | [Option('w', "receive-work-duration", Required = false, HelpText = "Work simulation delay between receive and completion (milliseconds, default 0, no work)")]
68 | public int WorkDuration { get; private set; } = 0;
69 |
70 | public void PrintSettings()
71 | {
72 | Console.WriteLine("Settings:");
73 | Console.WriteLine("{0}: {1}", "ReceivePaths", string.Join(",", this.ReceivePaths));
74 | Console.WriteLine("{0}: {1}", "SendPaths", this.SendPath);
75 | Console.WriteLine("{0}: {1}", "MessageCount", this.MessageCount);
76 | Console.WriteLine("{0}: {1}", "MessageSizeInBytes", this.MessageSizeInBytes);
77 | Console.WriteLine("{0}: {1}", "SenderCount", this.SenderCount);
78 | Console.WriteLine("{0}: {1}", "SendBatchCount", this.SendBatchCount);
79 | Console.WriteLine("{0}: {1}", "MaxInflightSends", this.CfgMaxInflightSends);
80 | Console.WriteLine("{0}: {1}", "ReceiveMode", this.ReceiveMode);
81 | Console.WriteLine("{0}: {1}", "ReceiverCount", this.ReceiverCount);
82 | Console.WriteLine("{0}: {1}", "ReceiveBatchCount", this.ReceiveBatchCount);
83 | Console.WriteLine("{0}: {1}", "ReceiveMode", this.ReceiveMode);
84 | Console.WriteLine("{0}: {1}", "MaxInflightReceives", this.CfgMaxInflightReceives);
85 | Console.WriteLine("{0}: {1}", "MetricsDisplayFrequency", this.MetricsDisplayFrequency);
86 | Console.WriteLine("{0}: {1}", "WorkDuration", this.WorkDuration);
87 |
88 | Console.WriteLine();
89 | }
90 |
91 | [Usage()]
92 | public static IEnumerable Examples
93 | {
94 | get
95 | {
96 | yield return new Example("queue scenario", new Settings { ConnectionString = "{Connection-String-with-EntityPath}" });
97 | yield return new Example("topic scenario", new Settings { ConnectionString = "{Connection-String}", SendPath = "{Topic-Name}", ReceivePaths = new string[] { "{Topic-Name}/subscriptions/{Subscription-Name-1}", "{Topic-Name}/subscriptions/{Subscription-Name-2}" } });
98 | }
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/ThroughputTest_v2/ServiceBusThroughputTestLib/Sender.cs:
--------------------------------------------------------------------------------
1 | //---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
5 | // EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
6 | // OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
7 | //---------------------------------------------------------------------------------
8 |
9 | namespace ServiceBusThroughputTestLib
10 | {
11 | using System;
12 | using System.Collections.Generic;
13 | using System.Diagnostics;
14 | using System.Text;
15 | using System.Threading;
16 | using System.Threading.Tasks;
17 | using Azure.Messaging.ServiceBus;
18 | using Extensions;
19 | using ServiceBusThroughputTest.Common;
20 |
21 | public class Sender
22 | {
23 | string connectionString;
24 | string queueName;
25 |
26 | int payloadSize = 600;
27 | int callIntervalMS = 0;
28 | int batchSize = 1;
29 | int sendersCount = 1;
30 | int concurrentCalls = 10;
31 | ILogger logger = null;
32 |
33 | public Sender(string connectionString, string queueName, int payloadSize, int batchSize, int callIntervalMS, int concurrentCalls, int sendersCount, ILogger logger = null)
34 | {
35 | this.connectionString = connectionString;
36 | this.queueName = queueName;
37 | this.payloadSize = payloadSize;
38 | this.batchSize = batchSize;
39 | this.callIntervalMS = callIntervalMS;
40 | this.concurrentCalls = concurrentCalls;
41 | this.sendersCount = sendersCount;
42 | this.logger = logger;
43 | }
44 |
45 | public async Task Run(CancellationToken cancellationToken)
46 | {
47 | ServiceBusClient[] clients = new ServiceBusClient[this.sendersCount];
48 | try
49 | {
50 | Task[] sendTasks = new Task[this.sendersCount];
51 | var senders = new ServiceBusSender[this.sendersCount];
52 |
53 | for (var i = 0; i < this.sendersCount; i++)
54 | {
55 | clients[i] = new ServiceBusClient(connectionString);
56 |
57 | senders[i] = clients[i].CreateSender(queueName);
58 | sendTasks[i] = Start(senders[i], cancellationToken);
59 | }
60 |
61 | await Task.WhenAll(sendTasks);
62 | }
63 | catch (Exception e)
64 | {
65 | logger?.AddTrace(e.ToString());
66 | }
67 | finally
68 | {
69 | Parallel.ForEach(clients, async client =>
70 | {
71 | await client.DisposeAsync();
72 | });
73 | }
74 |
75 | if (logger != null)
76 | {
77 | logger.Dispose();
78 | logger = null;
79 | }
80 | }
81 |
82 | async Task Start(ServiceBusSender sender, CancellationToken cancellationToken)
83 | {
84 | // Create payload
85 | byte[] payload = UTF8Encoding.ASCII.GetBytes(new string('a', payloadSize));
86 | ServiceBusMessage msg = new ServiceBusMessage(payload);
87 | var batch = new List();
88 | for (var i = 0; i < this.batchSize; i++)
89 | {
90 | batch.Add(msg);
91 | }
92 |
93 | var semaphore = new SemaphoreSlim(this.concurrentCalls);
94 | var sw = Stopwatch.StartNew();
95 |
96 | // Loop until cancelled
97 | while (!cancellationToken.IsCancellationRequested)
98 | {
99 | try
100 | {
101 | await semaphore.WaitAsync().ConfigureAwait(false);
102 |
103 | var sessionId = logger?.StartRecordTime();
104 |
105 | Fork(sender.SendMessagesAsync(batch, cancellationToken).ContinueWith(
106 | async (t) =>
107 | {
108 | double timeSpentMS_d = 0.0;
109 |
110 | if (sessionId != null)
111 | {
112 | timeSpentMS_d = logger.StopRecordTimeAndGetElapsedMs(sessionId.Value);
113 | }
114 |
115 | if (t.IsFaulted)
116 | {
117 | semaphore.Release();
118 |
119 | t.Exception.HandleExceptions(logger, sessionId, cancellationToken);
120 | }
121 | else
122 | {
123 | // Delay upto time interval if needed
124 | if (callIntervalMS > 0)
125 | {
126 | if (timeSpentMS_d < callIntervalMS - 1)
127 | {
128 | await Task.Delay(callIntervalMS - (int)timeSpentMS_d - 1);
129 | }
130 | }
131 |
132 | semaphore.Release();
133 |
134 | if (sessionId != null)
135 | {
136 | logger.IncrementActionCount(sessionId.Value);
137 | logger.IncrementMetricValueBy(sessionId.Value, batchSize);
138 | logger.DisposeSession(sessionId.Value);
139 | }
140 | }
141 |
142 | }));
143 | }
144 | catch (Exception e)
145 | {
146 | logger?.AddTrace(e.ToString());
147 | }
148 | finally
149 | {
150 | }
151 | }
152 |
153 | await sender.DisposeAsync();
154 | }
155 |
156 | static void Fork(Task t)
157 | {
158 | t.ContinueWith((_) => { });
159 | }
160 | }
161 |
162 | }
163 |
--------------------------------------------------------------------------------
/ThroughputTest/PerformanceApp.cs:
--------------------------------------------------------------------------------
1 | //---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
5 | // EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
6 | // OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
7 | //---------------------------------------------------------------------------------
8 |
9 | namespace ThroughputTest
10 | {
11 | using LinqStatistics;
12 | using System;
13 | using System.Collections.Generic;
14 | using System.Diagnostics;
15 | using System.Linq;
16 | using System.Reactive.Linq;
17 | using System.Threading;
18 | using System.Threading.Tasks;
19 |
20 | sealed class ServiceBusPerformanceApp
21 | {
22 | readonly Settings settings;
23 | readonly Metrics metrics;
24 | readonly CancellationTokenSource cancellationTokenSource;
25 | readonly List tasks;
26 | private IDisposable sendMetrics;
27 | private IDisposable receiveMetrics;
28 |
29 | public ServiceBusPerformanceApp(Settings settings, Metrics metrics)
30 | {
31 | this.settings = settings;
32 | this.metrics = metrics;
33 | this.cancellationTokenSource = new CancellationTokenSource();
34 | this.tasks = new List();
35 | }
36 |
37 |
38 | public async Task Run(params Experiment[] experiments)
39 | {
40 | this.settings.PrintSettings();
41 |
42 | tasks.Add(new ReceiverTask(settings, this.metrics, this.cancellationTokenSource.Token));
43 | tasks.Add(new SenderTask(settings, this.metrics, this.cancellationTokenSource.Token));
44 |
45 | Console.WriteLine("Starting...");
46 | Console.WriteLine();
47 |
48 | long sendTotal = 0, receiveTotal = 0;
49 | int windowLengthSecs = (int)this.settings.MetricsDisplayFrequency*2;
50 | if (this.settings.SenderCount > 0)
51 | {
52 | Console.ForegroundColor = ConsoleColor.Yellow;
53 | Console.Write("S|{0,10:0.00}|{1,10:0.00}|{2,5}|{3,5}|", "pstart", "pend", "sbc", "mifs");
54 | Console.Write("{0,10:0.00}|{1,10:0.00}|{2,10:0.00}|{3,10:0.00}|{4,10:0.00}|", "snd.avg", "snd.med", "snd.dev", "snd.min", "snd.max");
55 | Console.Write("{0,10:0.00}|{1,10:0.00}|{2,10:0.00}|{3,10:0.00}|{4,10:0.00}|", "gld.avg", "gld.med", "gld.dev", "gld.min", "gld.max");
56 | Console.WriteLine("{0,10}|{1,10}|{2,10}|{3,10}|{4,10}|{5,10}|", "msg/s", "total", "sndop", "errs", "busy", "overall");
57 | this.sendMetrics = ((IObservable)metrics)
58 | .Buffer(TimeSpan.FromSeconds(windowLengthSecs), TimeSpan.FromSeconds(windowLengthSecs/2))
59 | .Subscribe((list) =>
60 | {
61 | if (list.Count == 0)
62 | return;
63 | lock (Console.Out)
64 | {
65 | Console.ForegroundColor = ConsoleColor.Yellow;
66 | Console.Write("S|{0,10}|{1,10}|{2,5}|{3,5}|", list.First().Tick / Stopwatch.Frequency+1, list.Last().Tick / Stopwatch.Frequency+1, this.settings.SendBatchCount, this.settings.MaxInflightSends.Value);
67 | WriteStat(list, i => i.SendDuration100ns, Stopwatch.Frequency / 1000.0);
68 | WriteStat(list, i => i.GateLockDuration100ns, Stopwatch.Frequency / 10000.0);
69 | var msgs = list.Sum(i => i.Messages);
70 | sendTotal += msgs;
71 | Console.WriteLine("{0,10:0.00}|{1,10}|{2,10}|{3,10}|{4,10}|{5,10}|", list.Sum(i => i.Messages) / (double)windowLengthSecs, msgs, list.Sum(i => i.Sends), list.Sum(i => i.Errors), list.Sum(i => i.BusyErrors), sendTotal);
72 | }
73 | });
74 | }
75 | if (this.settings.ReceiverCount > 0)
76 | {
77 | Console.ForegroundColor = ConsoleColor.Cyan;
78 | Console.Write("R|{0,10:0.00}|{1,10:0.00}|{2,5}|{3,5}|", "pstart", "pend", "rbc", "mifr");
79 | Console.Write("{0,10:0.00}|{1,10:0.00}|{2,10:0.00}|{3,10:0.00}|{4,10:0.00}|", "rcv.avg", "rcv.med", "rcv.dev", "rcv.min", "rcv.max");
80 | Console.Write("{0,10:0.00}|{1,10:0.00}|{2,10:0.00}|{3,10:0.00}|{4,10:0.00}|", "cpl.avg", "cpl.med", "cpl.dev", "cpl.min", "cpl.max");
81 | Console.WriteLine("{0,10}|{1,10}|{2,10}|{3,10}|{4,10}|{5,10}|", "msg/s", "total", "rcvop", "errs", "busy", "overall");
82 |
83 | this.receiveMetrics = ((IObservable)metrics)
84 | .Buffer(TimeSpan.FromSeconds(windowLengthSecs), TimeSpan.FromSeconds(windowLengthSecs/2))
85 | .Subscribe((list) =>
86 | {
87 | if (list.Count == 0)
88 | return;
89 | lock (Console.Out)
90 | {
91 | Console.ForegroundColor = ConsoleColor.Cyan;
92 | Console.Write("R|{0,10}|{1,10}|{2,5}|{3,5}|", list.First().Tick / Stopwatch.Frequency+1, list.Last().Tick / Stopwatch.Frequency+1, this.settings.ReceiveBatchCount, this.settings.MaxInflightReceives.Value);
93 | WriteStat(list, i => i.ReceiveDuration100ns, Stopwatch.Frequency / 1000.0);
94 | WriteStat(list, i => i.CompleteDuration100ns, Stopwatch.Frequency / 1000.0);
95 | var msgs = list.Sum(i => i.Messages);
96 | receiveTotal += msgs;
97 | Console.WriteLine("{0,10:0.00}|{1,10}|{2,10}|{3,10}|{4,10}|{5,10}|", list.Sum(i => i.Messages) / (double)windowLengthSecs, msgs, list.Sum(i => i.Receives), list.Sum(i => i.Errors), list.Sum(i => i.BusyErrors), receiveTotal);
98 | }
99 | });
100 | }
101 |
102 | Task runTasks = tasks.ParallelForEachAsync((t) => t.StartAsync());
103 | if (experiments != null && experiments.Length > 0)
104 | {
105 | var experimentTask = RunExperiments(experiments, this.cancellationTokenSource.Token);
106 | await Task.WhenAll(new Task[] { experimentTask, runTasks });
107 | }
108 | else
109 | {
110 | await runTasks;
111 | }
112 | this.cancellationTokenSource.Cancel();
113 | this.tasks.ForEach((t) => t.Close());
114 | Console.ForegroundColor = ConsoleColor.White;
115 | }
116 |
117 | private Task RunExperiments(IEnumerable experiments, CancellationToken ct)
118 | {
119 | return Task.Run(async () =>
120 | {
121 | foreach (var experiment in experiments)
122 | {
123 | await Task.Delay(TimeSpan.FromSeconds(10)).ContinueWith((t) => StartExperiment(experiment)).ConfigureAwait(false);
124 | }
125 | });
126 | }
127 |
128 | private Task StartExperiment(Experiment experiment)
129 | {
130 | return experiment.Run().ContinueWith(t =>
131 | {
132 | if (t.Result != null)
133 | {
134 | return Task.Delay(TimeSpan.FromSeconds(10)).ContinueWith(t1 => StartExperiment(t.Result));
135 | }
136 | return Task.CompletedTask;
137 | });
138 | }
139 |
140 | void WriteStat(IList list, Func f, double scale)
141 | {
142 | if (list.Count > 1)
143 | {
144 | Console.Write("{0,10:0.00}|{1,10:0.00}|{2,10:0.00}|{3,10:0.00}|{4,10:0.00}|", list.Average(f) / scale, list.Median(f) / scale, list.StandardDeviationP(f) / scale, list.Min(f) / scale, list.Max(f) / scale);
145 | }
146 | }
147 |
148 | }
149 |
150 | }
151 |
--------------------------------------------------------------------------------
/ThroughputTest/SenderTask.cs:
--------------------------------------------------------------------------------
1 | //---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
5 | // EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
6 | // OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
7 | //---------------------------------------------------------------------------------
8 |
9 | namespace ThroughputTest
10 | {
11 | using System;
12 | using System.Collections.Generic;
13 | using System.Diagnostics;
14 | using System.Threading;
15 | using System.Threading.Tasks;
16 | using System.Net.Sockets;
17 | using Azure.Messaging.ServiceBus;
18 |
19 | sealed class SenderTask : PerformanceTask
20 | {
21 | readonly List senders;
22 |
23 | public SenderTask(Settings settings, Metrics metrics, CancellationToken cancellationToken)
24 | : base(settings, metrics, cancellationToken)
25 | {
26 | this.senders = new List();
27 | }
28 |
29 | protected override Task OnOpenAsync()
30 | {
31 | return Task.CompletedTask;
32 | }
33 |
34 | protected override Task OnStartAsync()
35 | {
36 | for (int i = 0; i < this.Settings.SenderCount; i++)
37 | {
38 | this.senders.Add(Task.Run(SendTask));
39 | }
40 | return Task.WhenAll(senders);
41 | }
42 |
43 | async Task SendTask()
44 | {
45 | var client = new ServiceBusClient(this.Settings.ConnectionString);
46 | ServiceBusSender sender = client.CreateSender(this.Settings.SendPath);
47 | var payload = new byte[this.Settings.MessageSizeInBytes];
48 | var semaphore = new DynamicSemaphoreSlim(this.Settings.MaxInflightSends.Value);
49 | var done = new SemaphoreSlim(1);
50 | done.Wait();
51 | long totalSends = 0;
52 |
53 | this.Settings.MaxInflightSends.Changing += (a, e) => AdjustSemaphore(e, semaphore);
54 | var sw = Stopwatch.StartNew();
55 |
56 | // first send will fail out if the cxn string is bad
57 | await sender.SendMessageAsync(new ServiceBusMessage(payload) { TimeToLive = TimeSpan.FromMinutes(5) });
58 |
59 | for (int j = 0; j < Settings.MessageCount && !this.CancellationToken.IsCancellationRequested; j++)
60 | {
61 | var sendMetrics = new SendMetrics() { Tick = sw.ElapsedTicks };
62 |
63 | var nsec = sw.ElapsedTicks;
64 | semaphore.Wait();
65 | //await semaphore.WaitAsync().ConfigureAwait(false);
66 | sendMetrics.InflightSends = this.Settings.MaxInflightSends.Value - semaphore.CurrentCount;
67 | sendMetrics.GateLockDuration100ns = sw.ElapsedTicks - nsec;
68 |
69 | if (Settings.SendDelay > 0)
70 | {
71 | await Task.Delay(Settings.SendDelay);
72 | }
73 | if (Settings.SendBatchCount <= 1)
74 | {
75 | sender.SendMessageAsync(new ServiceBusMessage(payload) { TimeToLive = TimeSpan.FromMinutes(5) })
76 | .ContinueWith(async (t) =>
77 | {
78 | if (t.IsFaulted || t.IsCanceled)
79 | {
80 | await HandleExceptions(semaphore, sendMetrics, t.Exception);
81 | }
82 | else
83 | {
84 | sendMetrics.SendDuration100ns = sw.ElapsedTicks - nsec;
85 | sendMetrics.Sends = 1;
86 | sendMetrics.Messages = 1;
87 | semaphore.Release();
88 | Metrics.PushSendMetrics(sendMetrics);
89 | }
90 | if (Interlocked.Increment(ref totalSends) >= Settings.MessageCount)
91 | {
92 | done.Release();
93 | }
94 | }).Fork();
95 | }
96 | else
97 | {
98 | var batch = new List();
99 | for (int i = 0; i < Settings.SendBatchCount && j < Settings.MessageCount && !this.CancellationToken.IsCancellationRequested; i++, j++)
100 | {
101 | batch.Add(new ServiceBusMessage(payload) { TimeToLive = TimeSpan.FromMinutes(5) });
102 | }
103 | sender.SendMessagesAsync(batch)
104 | .ContinueWith(async (t) =>
105 | {
106 | if (t.IsFaulted || t.IsCanceled)
107 | {
108 | await HandleExceptions(semaphore, sendMetrics, t.Exception);
109 | }
110 | else
111 | {
112 | sendMetrics.SendDuration100ns = sw.ElapsedTicks - nsec;
113 | sendMetrics.Sends = 1;
114 | sendMetrics.Messages = Settings.SendBatchCount;
115 | semaphore.Release();
116 | Metrics.PushSendMetrics(sendMetrics);
117 | }
118 | if (Interlocked.Increment(ref totalSends) >= Settings.MessageCount)
119 | {
120 | done.Release();
121 | }
122 | }).Fork();
123 | }
124 | }
125 | await done.WaitAsync();
126 | }
127 |
128 | static void AdjustSemaphore(Observable.ChangingEventArgs e, DynamicSemaphoreSlim semaphore)
129 | {
130 | if (e.NewValue > e.OldValue)
131 | {
132 | for (int i = e.OldValue; i < e.NewValue; i++)
133 | {
134 | semaphore.Grant();
135 | }
136 | }
137 | else
138 | {
139 | for (int i = e.NewValue; i < e.OldValue; i++)
140 | {
141 | semaphore.Revoke();
142 | }
143 | }
144 | }
145 |
146 | private async Task HandleExceptions(DynamicSemaphoreSlim semaphore, SendMetrics sendMetrics, AggregateException ex)
147 | {
148 | bool wait = false;
149 | ex.Handle((x) =>
150 | {
151 | if (x is ServiceBusException sbException)
152 | {
153 | if (sbException.Reason == ServiceBusFailureReason.ServiceCommunicationProblem)
154 | {
155 | if (sbException.InnerException is SocketException socketException &&
156 | socketException.SocketErrorCode == SocketError.HostNotFound)
157 | {
158 | return false;
159 | }
160 | }
161 | if (sbException.Reason == ServiceBusFailureReason.ServiceBusy)
162 | {
163 | sendMetrics.BusyErrors = 1;
164 | if (!this.CancellationToken.IsCancellationRequested)
165 | {
166 | wait = true;
167 | }
168 | }
169 | else
170 | {
171 | sendMetrics.Errors = 1;
172 | }
173 | }
174 | return true;
175 | });
176 |
177 | if (wait)
178 | {
179 | await Task.Delay(3000, this.CancellationToken).ConfigureAwait(false);
180 | }
181 | semaphore.Release();
182 | Metrics.PushSendMetrics(sendMetrics);
183 |
184 | }
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/ThroughputTest_v2/README.md:
--------------------------------------------------------------------------------
1 | # Azure Service Bus Throughput Performance Test
2 |
3 | In the v2 version, we have simplified and done some refactoring of the original console app. The gut of the app is still exactly same as before.
4 | We now have two common libraries that the console app depends on. This allows the core libraries to be hosted not only in console (as in the v2 version in the repo) but also
5 | in containerized environments such as Service Fabric for automated scaling and generating/simulating higher throughput/load scenarios
6 | (without the need of having to spin up vms manually).
7 |
8 | The console app now also prints out P99 metric which depending on the scenario is sometimes more important than average.
9 |
10 | This version is tailored more for windows and azure containerized environments so if you wish to use it on Linux, v1 is still a better option.
11 |
12 | ## Building the Tool
13 |
14 | The tool is a .NET framework project that can produce standalone executable for Windows.
15 |
16 | To build, use MSBUILD to run following commands:
17 |
18 | ```
19 | MSBUILD /t:restore
20 | MSBUILD \ServiceBusThroughputTest.sln /property:Configuration=Release
21 | ```
22 | The output application can be found in the ```ThroughpurTest_v2\ServiceBusThroughputTest\bin\Release```subdirectory.
23 |
24 | ## Running the Tool
25 |
26 | You can run the tool locally from your own machine or you can run it from within an Azure VM.
27 |
28 | The only required command line arguments are a connection string and a send path. If the connection string includes
29 | an EntityPath property for a queue, the send path can be omitted. If the receive path is not given, the tool assumes
30 | the send path and the receive path to be the same, which is sufficient for queues. For topics, the subscription(s) must
31 | be given with explicit receive path options.
32 |
33 | You can test any valid Service Bus topology with this tool, including Topics with subscriptions and chained
34 | entities with auto-forward set up between them. Therefore, the send path and the receive path do not have to be
35 | on the same entity. You can also receive from dead-letter queues.
36 |
37 | | Scenario | Arguments |
38 | |------------------------------|-------------------------------------------------------------------------------|
39 | | Send to and receive from a queue |```ThroughputTest -C {connection-string} -S myQueueName ``` |
40 | | Send to a topic and receive from a subscription | ``` ThroughputTest -C {connection-string} -S myTopicName -R myTopicName/subscriptions/mySubName ``` |
41 | | Send a queue |```ThroughputTest -C {connection-string} -S myQueueName -r 0 ``` |
42 | | Receive from a queue |```ThroughputTest -C {connection-string} -S myQueueName -s 0 ``` |
43 |
44 | ## Output
45 |
46 | The tool prints out interleaved statistics for sends and receives. Send information is prefixed with S (and in yellow),
47 | receive information is prefixed with R and printed in cyan. The columns are separated with the pipe symbol and therefore
48 | parseable.
49 |
50 | ### Send output columns
51 |
52 | | Column | Description
53 | |----------|---------------------------------------------------------------------------
54 | | pstart | Begin of the data recording for this row (seconds from start of run)
55 | | pend | End of data recording for this row
56 | | sbc | Send batch count
57 | | mifs | Max inflight sends
58 | | snd.avg | Average send duration (to acknowledgement receipt) in milliseconds
59 | | snd.med | Median send duration
60 | | snd.dev | Standard deviation for send duration
61 | | snd.min | Minimum send duration
62 | | snd.max | Maximum send duration
63 | | gld.avg | Average gate lock duration in milliseconds. This measure tracks whether the internal async thread pool queue gets backed up. If this value shoots up, you should reduce the number of concurrent inflight sends, because the application sends more than what can be put on the wire.
64 | | gld.med | Median gate lock duration
65 | | gld.dev | Standard deviation for gate lock duration
66 | | gld.min | Minimum gate lock duration
67 | | gld.max | Maximum gate lock duration
68 | | msg/s | Throughput in messages per second
69 | | total | Total messages sent in this period
70 | | sndop | Total send operations in this period
71 | | errs | Errors
72 | | busy | Busy errors
73 | | overall | Total messages sent in this run
74 |
75 | ### Receive output columns
76 |
77 | | Column | Description
78 | |----------|---------------------------------------------------------------------------
79 | | pstart | Begin of the data recording for this row (seconds from start of run)
80 | | pend | End of data recording for this row
81 | | rbc | Receive batch count
82 | | mifr | Max inflight receives
83 | | rcv.avg | Average receive duration (to message receipt) in milliseconds
84 | | rcv.med | Median receive duration
85 | | rcv.dev | Standard deviation for receive duration
86 | | rcv.min | Minimum receive duration
87 | | rcv.max | Maximum receive duration
88 | | cpl.avg | Average completion duration in milliseconds.
89 | | cpl.med | Median completion duration
90 | | cpl.dev | Standard deviation for completion duration
91 | | cpl.min | Minimum completion duration
92 | | cpl.max | Maximum completion duration
93 | | msg/s | Throughput in messages per second
94 | | total | Total messages received in this period
95 | | rcvop | Total receive operations in this period
96 | | errs | Errors
97 | | busy | Busy errors
98 | | overall | Total messages sent in this run
99 |
100 |
101 | ## Options
102 |
103 | When only called with a connection string and send/receive paths, the tool will concurrently send and receive messages
104 | to/from the chosen Service Bus entity.
105 |
106 | If you just want to send messages, set the receiver count to zero with **-r 0**. If you only want to receive messages,
107 | set the sender count to zero with **-s 0**.
108 |
109 | The biggest throughput boosts yield the enabling of send and receive batching, meaning the sending and receiving of
110 | several messages in one operation. This oprtion will maximize throughput and show you the practical limits, but you
111 | should always keep an eye on whether batching is a practical approach for your specific solution.
112 |
113 | The "inflight-sends" and "inflight-receives" options also have very significant throughput impact. "inflight-sends"
114 | tracks how many messages are being sent asynchronously and in a pipelined fashion while waiting for the operations
115 | to complete. The "inflight-receives" option controls how many messages are being received and processed concurrently.
116 |
117 | The further options are listed below:
118 |
119 | | Parameter | Description
120 | |-------------------------------|-------------------------------------------------------------------------------
121 | | **-C, --connection-string** | **Required**. Connection string
122 | | -S, --send-path | Send path. Queue or topic name, unless set in connection string EntityPath.
123 | | -R, --receive-paths | Receive paths. Mandatory for receiving from topic subscriptions. Must be {topic}/subscriptions/{subscription-name} or {queue-name}
124 | | -n, --number-of-messages | Number of messages to send (default 1000000)
125 | | -b, --message-size-bytes | Bytes per message (default 1024)
126 | | -f, --frequency-metrics | Frequency of metrics display (seconds, default 10s)
127 | | -m, --receive-mode | Receive mode.'PeekLock' (default) or 'ReceiveAndDelete'
128 | | -r, --receiver-count | Number of concurrent receivers (default 1)
129 | | -e, --prefetch-count | Prefetch count (default 0)
130 | | -t, --send-batch-count | Number of messages per batch (default 0, no batching)
131 | | -s, --sender-count | Number of concurrent senders (default 1)
132 | | -d, --send-delay | Delay between sends of any sender (milliseconds, default 0)
133 | | -i, --inflight-sends | Maximum numbers of concurrent in-flight send operations (default 1)
134 | | -j, --inflight-receives | Maximum number of concurrent in-flight receive operations per receiver (default 1)
135 | | -v, --receive-batch-count | Max number of messages per batch (default 0, no batching)
136 | | -w, --receive-work-duration | Work simulation delay between receive and completion (milliseconds, default 0, no work)
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
--------------------------------------------------------------------------------
/ThroughputTest_v2/ServiceBusThroughputTestLib/Receiver.cs:
--------------------------------------------------------------------------------
1 | //---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
5 | // EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
6 | // OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
7 | //---------------------------------------------------------------------------------
8 |
9 | namespace ServiceBusThroughputTestLib
10 | {
11 | using System;
12 | using System.Collections.Generic;
13 | using System.Diagnostics;
14 | using System.Linq;
15 | using System.Threading;
16 | using System.Threading.Tasks;
17 | using Azure.Messaging.ServiceBus;
18 | using ServiceBusThroughputTest.Common;
19 | using ServiceBusThroughputTestLib.Extensions;
20 |
21 | public class Receiver
22 | {
23 | string connectionString;
24 | string queueName;
25 | int prefetchCount = 0;
26 | int batchSize = 1;
27 | int receiversCount = 1;
28 | int concurrentCalls = 1;
29 | ILogger logger = null;
30 | int callIntervalMS = 1000;
31 | bool isReceiveAndDelete = false;
32 |
33 | public Receiver(string connectionString, string queueName, int prefetchCount, int batchSize, int concurrentCalls, int receiversCount, int callIntervalMS, bool isReceiveAndDelete = false, ILogger logger = null)
34 | {
35 | this.connectionString = connectionString;
36 | this.queueName = queueName;
37 | this.prefetchCount = prefetchCount;
38 | this.concurrentCalls = concurrentCalls;
39 | this.receiversCount = receiversCount;
40 | this.batchSize = batchSize;
41 | this.callIntervalMS = callIntervalMS;
42 | this.isReceiveAndDelete = isReceiveAndDelete;
43 | this.logger = logger;
44 | }
45 |
46 | public async Task Run(CancellationToken cancellationToken)
47 | {
48 | ServiceBusClient[] clients = new ServiceBusClient[this.receiversCount];
49 |
50 | try
51 | {
52 | ServiceBusReceiveMode receiveMode = this.isReceiveAndDelete ? ServiceBusReceiveMode.ReceiveAndDelete : ServiceBusReceiveMode.PeekLock;
53 |
54 | // create the options to use for configuring the processor
55 | var options = new ServiceBusReceiverOptions
56 | {
57 | // By default or when AutoCompleteMessages is set to true, the processor will complete the message after executing the message handler
58 | // Set AutoCompleteMessages to false to [settle messages](https://docs.microsoft.com/en-us/azure/service-bus-messaging/message-transfers-locks-settlement#peeklock) on your own.
59 | // In both cases, if the message handler throws an exception without settling the message, the processor will abandon the message.
60 | ReceiveMode = receiveMode,
61 | PrefetchCount = prefetchCount,
62 | };
63 |
64 | Task[] receiveTasks = new Task[this.receiversCount];
65 |
66 | for (var i = 0; i < this.receiversCount; i++)
67 | {
68 | clients[i] = new ServiceBusClient(connectionString);
69 |
70 | ServiceBusReceiver receiver;
71 | if (queueName.Contains(":"))
72 | {
73 | var topic = queueName.Split(':')[0];
74 | var sub = queueName.Split(':')[1];
75 | receiver = clients[i].CreateReceiver(topic, sub, options);
76 | }
77 | else
78 | {
79 | receiver = clients[i].CreateReceiver(queueName, options);
80 | }
81 |
82 | receiveTasks[i] = Start(receiver, cancellationToken);
83 | }
84 |
85 | await Task.WhenAll(receiveTasks);
86 | }
87 | catch (Exception e)
88 | {
89 | logger?.AddTrace(e.ToString());
90 | }
91 | finally
92 | {
93 | //Parallel.ForEach(clients, async client =>
94 | //{
95 | // await client.DisposeAsync();
96 | //});
97 | }
98 |
99 | if (logger != null)
100 | {
101 | logger.AddTrace($"cancellationToken.IsCancellationRequested={cancellationToken.IsCancellationRequested}");
102 | logger.Dispose();
103 | logger = null;
104 | }
105 | }
106 |
107 | async Task Start(ServiceBusReceiver receiver, CancellationToken cancellationToken)
108 | {
109 | var semaphore = new SemaphoreSlim(this.concurrentCalls);
110 | var sw = Stopwatch.StartNew();
111 |
112 | while (!cancellationToken.IsCancellationRequested)
113 | {
114 | try
115 | {
116 | await semaphore.WaitAsync().ConfigureAwait(false);
117 |
118 | var sessionId = logger?.StartRecordTime();
119 |
120 | Fork(receiver.ReceiveMessagesAsync(this.batchSize, TimeSpan.FromMilliseconds(this.callIntervalMS)).ContinueWith(
121 | async (t) =>
122 | {
123 | if (sessionId != null)
124 | {
125 | logger.StopRecordTimeAndGetElapsedMs(sessionId.Value);
126 | }
127 |
128 | if (!t.IsFaulted && !this.isReceiveAndDelete && t.Result?.Any() == true)
129 | {
130 | // following function will release semaphore so no need to do it here
131 | await CompleteMessagesAndReleaseSemaphore(t.Result, receiver, semaphore, cancellationToken, logger, sessionId);
132 | }
133 | else
134 | {
135 | semaphore.Release();
136 |
137 | if (t.IsFaulted)
138 | {
139 | t.Exception.HandleExceptions(logger, sessionId, cancellationToken);
140 | }
141 | else if (sessionId != null)
142 | {
143 | logger.IncrementActionCount(sessionId.Value);
144 | logger.IncrementMetricValueBy(sessionId.Value, t.Result?.Count ?? 0);
145 | logger.DisposeSession(sessionId.Value);
146 | }
147 | }
148 | }));
149 | }
150 | catch (Exception ex)
151 | {
152 | logger?.AddTrace(ex.ToString());
153 | }
154 | finally
155 | {
156 | }
157 | }
158 |
159 | await receiver.CloseAsync();
160 | }
161 |
162 | static async Task CompleteMessagesAndReleaseSemaphore(IReadOnlyList messages, ServiceBusReceiver receiver, SemaphoreSlim semaphore, CancellationToken cancellationToken, ILogger logger = null, Guid? sessionId = null)
163 | {
164 | try
165 | {
166 | var completeTasks = new Task[messages.Count];
167 |
168 | if (sessionId != null && logger != null)
169 | {
170 | logger.StartCompletionRecordTime(sessionId.Value);
171 | }
172 |
173 | for (var i = 0; i < messages.Count; i++)
174 | {
175 | completeTasks[i] = receiver.CompleteMessageAsync(messages[i]);
176 | }
177 |
178 | await Task.WhenAll(completeTasks).ContinueWith(t =>
179 | {
180 | if (sessionId != null)
181 | {
182 | logger?.StopCompletionRecordTimeAndGetElapsedMs(sessionId.Value);
183 | }
184 |
185 | semaphore.Release();
186 |
187 | if (t.IsFaulted)
188 | {
189 | t.Exception.HandleExceptions(logger, sessionId, cancellationToken);
190 | }
191 | else
192 | {
193 | if (sessionId != null)
194 | {
195 | logger.IncrementActionCount(sessionId.Value);
196 | logger.IncrementMetricValueBy(sessionId.Value, messages.Count);
197 | logger.DisposeSession(sessionId.Value);
198 | }
199 | }
200 | });
201 | }
202 | catch (Exception e)
203 | {
204 | semaphore.Release();
205 |
206 | logger?.AddTrace($"Error completing message/s - {e}");
207 | }
208 | }
209 |
210 | static void Fork(Task t)
211 | {
212 | t.ContinueWith((_) => { });
213 | }
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/ThroughputTest/README.md:
--------------------------------------------------------------------------------
1 | # Azure Service Bus Throughput Performance Test
2 |
3 | This code sample illustrates the available throughput performance options for Azure Service Bus clients
4 | and also allows for running experiments that dynamically adjust some tuning parameters.
5 |
6 | > This tool is meant to be used with Azure Service Bus Premium and not with Azure Service Bus Standard.
7 | > Azure Service Bus Premium is designed to provide predictable performance, meaning that the results
8 | > you measure with this tool are representative of the performance you can expect for your applications.
9 |
10 | The sample assumes that you are generally familiar with the Service Bus .NET SDK and with how to set up
11 | namespaces and entities within those namespaces.
12 |
13 | The sample can either send and receive messages from a single instance, or just act as either sender or receiver,
14 | allowing simulation of different scenarios.
15 |
16 | The sending side supports sending messages singly or in batches, and it supports pipelining of send operations
17 | whereby up to a certain number of messages are kept in flight and their acknowledgement is handled asynchronously.
18 | You can start one or multiple concurrent senders, and each sender can be throttled by imposing a pause between
19 | individual send operations.
20 |
21 | The receive side supports the "ReceiveAndDelete" and "PeekLock" receive modes, one or multiple concurrent
22 | receive loops, single or batch receive operations, and single or batch completion. You can simulate having
23 | multiple concurrent handlers on a receiver and you can also impose a delay to simulate work.
24 |
25 | ## What to expect
26 |
27 | This sample is specifically designed to test throughput limits of queues and topics/subscriptions in Service Bus
28 | namespaces. It does not measure end-to-end latency, meaning how fast messages can pass through Service Bus under
29 | optimal conditions. The goals of achieving maximum throughput and the lowest end-to-end latency are fundamentally
30 | at odds, as you might know first-hand from driving a car. Either you can go fast on a street with light traffic, or
31 | the street can handle a maximum capacity of cars at the same time, but at the cost of individual speed.
32 | The goal of this sample is to find out where the capacity limits are.
33 |
34 | As discussed in the [product documentation](https://docs.microsoft.com/azure/service-bus-messaging/message-transfers-locks-settlement),
35 | network latency has very significant impact on the achievable throughput. If you are running this sample from your
36 | development workstation, throughput will be substantially lower than from within an Azure VM. If you want to
37 | test limits for a scenario where the Service Bus client will reside inside Azure, you should also run this test
38 | inside Azure on a Windows or Linux VM that resides within the same region as the Service Bus namespace you
39 | want to test.
40 |
41 | In an in-region setup, and with ideal parameters, you can achieve send rates exceeding 20000 msg/sec at 1024 bytes per message
42 | from a single client, meaning that a 1GB queue will fill up in under a minute. Also be aware that receive operations are
43 | generally more costly and therefore slower than send operations, which means that a test with maximum send
44 | pressure (several senders using batching) may not be sustainable for longer periods because the receivers might not be
45 | able to keep up.
46 |
47 | ## Building the Tool
48 |
49 | The tool is a .NET Core project that can produce standalone executables for Linux and Windows, whereby we'll assume x64 targets.
50 |
51 | For Linux, with the .NET Core 3.0 SDK installed, run:
52 |
53 | ```
54 | dotnet publish -c Release -f netcoreapp3.0 -r linux-x64
55 | ```
56 | The output application can be found in the ```bin/Release/netcoreapp3.0/linux-x64/publish```subdirectory.
57 |
58 | For Windows, run:
59 |
60 | ```
61 | dotnet publish -c Release -f netcoreapp3.0 -r win-x64
62 | ```
63 | The output application can be found in the ```bin\Release\netcoreapp3.0\win-x64\publish```subdirectory.
64 |
65 | ## Running the Tool
66 |
67 | You can run the tool locally from your own machine or you can run it from within an Azure VM. You should run the
68 | tool on the platform you are targeting, because performance differs between Linux and Windows due to their different
69 | I/O architectures and different implementation of the platform layer in .NET Core.
70 |
71 | The only required command line arguments are a connection string and a send path. If the connection string includes
72 | an EntityPath property for a queue, the send path can be omitted. If the receive path is not given, the tool assumes
73 | the send path and the receive path to be the same, which is sufficient for queues. For topics, the subscription(s) must
74 | be given with explicit receive path options.
75 |
76 | You can test any valid Service Bus topology with this tool, including Topics with subscriptions and chained
77 | entities with auto-forward set up between them. Therefore, the send path and the receive path do not have to be
78 | on the same entity. You can also receive from dead-letter queues.
79 |
80 | | Scenario | Arguments |
81 | |------------------------------|-------------------------------------------------------------------------------|
82 | | Send to and receive from a queue |```ThroughputTest -C {connection-string} -S myQueueName ``` |
83 | | Send to a topic and receive from a subscription | ``` ThroughputTest -C {connection-string} -S myTopicName -R myTopicName/subscriptions/mySubName ``` |
84 | | Send to a topic and receive from two subscriptions | ``` ThroughputTest -C {connection-string} -S myTopicName -R myTopicName/subscriptions/mySubNameA myTopicName/subscriptions/mySubNameB ``` |
85 | | Send a queue |```ThroughputTest -C {connection-string} -S myQueueName -r 0 ``` |
86 | | Receive from a queue |```ThroughputTest -C {connection-string} -S myQueueName -s 0 ``` |
87 |
88 | ## Output
89 |
90 | The tool prints out interleaved statistics for sends and receives. Send information is prefixed with S (and in yellow),
91 | receive information is prefixed with R and printed in cyan. The columns are separated with the pipe symbol and therefore
92 | parseable.
93 |
94 | ### Send output columns
95 |
96 | | Column | Description
97 | |----------|---------------------------------------------------------------------------
98 | | pstart | Begin of the data recording for this row (seconds from start of run)
99 | | pend | End of data recording for this row
100 | | sbc | Send batch count
101 | | mifs | Max inflight sends
102 | | snd.avg | Average send duration (to acknowledgement receipt) in milliseconds
103 | | snd.med | Median send duration
104 | | snd.dev | Standard deviation for send duration
105 | | snd.min | Minimum send duration
106 | | snd.max | Maximum send duration
107 | | gld.avg | Average gate lock duration in milliseconds. This measure tracks whether the internal async thread pool queue gets backed up. If this value shoots up, you should reduce the number of concurrent inflight sends, because the application sends more than what can be put on the wire.
108 | | gld.med | Median gate lock duration
109 | | gld.dev | Standard deviation for gate lock duration
110 | | gld.min | Minimum gate lock duration
111 | | gld.max | Maximum gate lock duration
112 | | msg/s | Throughput in messages per second
113 | | total | Total messages sent in this period
114 | | sndop | Total send operations in this period
115 | | errs | Errors
116 | | busy | Busy errors
117 | | overall | Total messages sent in this run
118 |
119 | ### Receive output columns
120 |
121 | | Column | Description
122 | |----------|---------------------------------------------------------------------------
123 | | pstart | Begin of the data recording for this row (seconds from start of run)
124 | | pend | End of data recording for this row
125 | | rbc | Receive batch count
126 | | mifr | Max inflight receives
127 | | rcv.avg | Average receive duration (to message receipt) in milliseconds
128 | | rcv.med | Median receive duration
129 | | rcv.dev | Standard deviation for receive duration
130 | | rcv.min | Minimum receive duration
131 | | rcv.max | Maximum receive duration
132 | | cpl.avg | Average completion duration in milliseconds.
133 | | cpl.med | Median completion duration
134 | | cpl.dev | Standard deviation for completion duration
135 | | cpl.min | Minimum completion duration
136 | | cpl.max | Maximum completion duration
137 | | msg/s | Throughput in messages per second
138 | | total | Total messages received in this period
139 | | rcvop | Total receive operations in this period
140 | | errs | Errors
141 | | busy | Busy errors
142 | | overall | Total messages sent in this run
143 |
144 |
145 | ## Options
146 |
147 | When only called with a connection string and send/receive paths, the tool will concurrently send and receive messages
148 | to/from the chosen Service Bus entity.
149 |
150 | If you just want to send messages, set the receiver count to zero with **-r 0**. If you only want to receive messages,
151 | set the sender count to zero with **-s 0**.
152 |
153 | The biggest throughput boosts yield the enabling of send and receive batching, meaning the sending and receiving of
154 | several messages in one operation. This oprtion will maximize throughput and show you the practical limits, but you
155 | should always keep an eye on whether batching is a practical approach for your specific solution.
156 |
157 | The "inflight-sends" and "inflight-receives" options also have very significant throughput impact. "inflight-sends"
158 | tracks how many messages are being sent asynchronously and in a pipelined fashion while waiting for the operations
159 | to complete. The "inflight-receives" option controls how many messages are being received and processed concurrently.
160 |
161 | The further options are listed below:
162 |
163 | | Parameter | Description
164 | |-------------------------------|-------------------------------------------------------------------------------
165 | | **-C, --connection-string** | **Required**. Connection string
166 | | -S, --send-path | Send path. Queue or topic name, unless set in connection string EntityPath.
167 | | -R, --receive-paths | Receive paths. Mandatory for receiving from topic subscriptions. Must be {topic}/subscriptions/{subscription-name} or {queue-name}
168 | | -n, --number-of-messages | Number of messages to send (default 1000000)
169 | | -b, --message-size-bytes | Bytes per message (default 1024)
170 | | -f, --frequency-metrics | Frequency of metrics display (seconds, default 10s)
171 | | -m, --receive-mode | Receive mode.'PeekLock' (default) or 'ReceiveAndDelete'
172 | | -r, --receiver-count | Number of concurrent receivers (default 1)
173 | | -e, --prefetch-count | Prefetch count (default 0)
174 | | -t, --send-batch-count | Number of messages per batch (default 0, no batching)
175 | | -s, --sender-count | Number of concurrent senders (default 1)
176 | | -d, --send-delay | Delay between sends of any sender (milliseconds, default 0)
177 | | -i, --inflight-sends | Maximum numbers of concurrent in-flight send operations (default 1)
178 | | -j, --inflight-receives | Maximum number of concurrent in-flight receive operations per receiver (default 1)
179 | | -v, --receive-batch-count | Max number of messages per batch (default 0, no batching)
180 | | -w, --receive-work-duration | Work simulation delay between receive and completion (milliseconds, default 0, no work)
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
--------------------------------------------------------------------------------
/ThroughputTest_v2/ServiceBusThroughputTest/Metrics.cs:
--------------------------------------------------------------------------------
1 | //---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
5 | // EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
6 | // OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
7 | //---------------------------------------------------------------------------------
8 |
9 | namespace ServiceBusThroughputTest
10 | {
11 | using ServiceBusThroughputTest.Common;
12 | using System;
13 | using System.Buffers;
14 | using System.Collections.Concurrent;
15 | using System.Collections.Generic;
16 | using System.Data.SqlTypes;
17 | using System.Diagnostics;
18 | using System.Threading;
19 | using System.Threading.Tasks;
20 | using System.Timers;
21 | using Timer = System.Timers.Timer;
22 |
23 | public sealed class Metrics : ILogger
24 | {
25 | public event EventHandler OnMetricsDisplay;
26 | readonly bool isSendMetric;
27 | readonly long maxMetricsSamplesPerDisplayInterval = 5000;
28 | readonly CancellationToken cancellationToken;
29 |
30 | ConcurrentDictionary sendSessions = new ConcurrentDictionary();
31 | ConcurrentDictionary receiveSessions = new ConcurrentDictionary();
32 | object timerLock = new object();
33 | object metricsLock = new object();
34 | ArrayPool sendMetricsPool = null;
35 | ArrayPool rxMetricsPool = null;
36 |
37 | SendMetrics[] activeSendMetricsBuffer = null; int sendMetricsBufferWriteIndex = -1;
38 | ReceiveMetrics[] activeReceiveMetricsBuffer = null; int rxMetricsBufferWriteIndex = -1;
39 |
40 | ObjectPool sendPool = null;
41 | ObjectPool rxPool = null;
42 | Timer timer;
43 | Stopwatch sw = Stopwatch.StartNew();
44 | private bool disposedValue;
45 |
46 | public Metrics(CancellationToken cancellationToken = default, bool isSendMetric = true, double metricsDisplayFrequency = 10.0, int maxMetricsSamplePerDisplayInterval = 5000)
47 | {
48 | this.isSendMetric = isSendMetric;
49 | this.cancellationToken = cancellationToken;
50 | this.maxMetricsSamplesPerDisplayInterval = maxMetricsSamplePerDisplayInterval;
51 |
52 | sendPool = new ObjectPool(
53 | () => new SendMetrics(),
54 | (s) =>
55 | {
56 | s.SendDuration100ns = s.Tick = 0;
57 | s.Sends = s.InflightSends = s.BusyErrors = s.Errors = s.Messages = s.Size = 0;
58 | },
59 | maxMetricsSamplesPerDisplayInterval);
60 |
61 | rxPool = new ObjectPool(
62 | () => new ReceiveMetrics(),
63 | (r) =>
64 | {
65 | r.ReceiveDuration100ns = r.Tick = r.CompleteDuration100ns = 0;
66 | r.Receives = r.Messages = r.CompleteCalls = r.Errors = r.BusyErrors = r.CompleteCalls = 0;
67 | },
68 | maxMetricsSamplesPerDisplayInterval);
69 |
70 | sendMetricsPool = ArrayPool.Create((int)maxMetricsSamplesPerDisplayInterval, 4);
71 | rxMetricsPool = ArrayPool.Create((int)maxMetricsSamplesPerDisplayInterval, 4);
72 |
73 | activeSendMetricsBuffer = sendMetricsPool.Rent((int)maxMetricsSamplesPerDisplayInterval);
74 | activeReceiveMetricsBuffer = rxMetricsPool.Rent((int)maxMetricsSamplesPerDisplayInterval);
75 | this.rxMetricsBufferWriteIndex = this.sendMetricsBufferWriteIndex = -1;
76 |
77 | timer = new Timer(metricsDisplayFrequency * 1000);
78 | timer.Elapsed += Timer_Elapsed;
79 |
80 | timer.Enabled = true;
81 | timer.AutoReset = true;
82 | timer.Start();
83 | }
84 |
85 | private void Timer_Elapsed(object sender, ElapsedEventArgs e)
86 | {
87 | lock (timerLock)
88 | {
89 | int sendMetricsCount = 0, rxMetricsCount = 0;
90 | SendMetrics[] sendBuffer = this.activeSendMetricsBuffer;
91 | ReceiveMetrics[] rxBuffer = this.activeReceiveMetricsBuffer;
92 |
93 | lock (this.metricsLock)
94 | {
95 | sendMetricsCount = this.sendMetricsBufferWriteIndex + 1;
96 | rxMetricsCount = this.rxMetricsBufferWriteIndex + 1;
97 | this.rxMetricsBufferWriteIndex = this.sendMetricsBufferWriteIndex = -1;
98 | this.activeSendMetricsBuffer = sendMetricsPool.Rent((int)maxMetricsSamplesPerDisplayInterval);
99 | this.activeReceiveMetricsBuffer = rxMetricsPool.Rent((int)maxMetricsSamplesPerDisplayInterval);
100 | }
101 |
102 | this.OnMetricsDisplay?.Invoke(
103 | this,
104 | new MetricsDisplayEventArgs
105 | {
106 | SendMetricsList = new ArraySegment(sendBuffer, 0, sendMetricsCount),
107 | ReceiveMetricsList = new ArraySegment(rxBuffer, 0, rxMetricsCount)
108 | });
109 |
110 | if (sendMetricsCount > 0)
111 | {
112 | Parallel.For(0, sendMetricsCount, index =>
113 | {
114 | sendPool.Return(sendBuffer[index]);
115 | });
116 | }
117 |
118 | if (rxMetricsCount > 0)
119 | {
120 | Parallel.For(0, rxMetricsCount, index =>
121 | {
122 | rxPool.Return(rxBuffer[index]);
123 | });
124 | }
125 |
126 | this.sendMetricsPool.Return(sendBuffer, true);
127 | this.rxMetricsPool.Return(rxBuffer, true);
128 | }
129 |
130 | if (cancellationToken.IsCancellationRequested)
131 | {
132 | timer.Enabled = false;
133 | }
134 | }
135 |
136 | public void DisposeSession(Guid sessionId)
137 | {
138 | if (sessionId == Guid.Empty)
139 | {
140 | return;
141 | }
142 |
143 | if (isSendMetric)
144 | {
145 | sendSessions.TryRemove(sessionId, out SendMetrics sendMetrics);
146 |
147 | lock (metricsLock)
148 | {
149 | if (sendMetricsBufferWriteIndex < this.activeSendMetricsBuffer.Length - 1)
150 | {
151 | sendMetricsBufferWriteIndex++;
152 |
153 | activeSendMetricsBuffer[sendMetricsBufferWriteIndex] = sendMetrics;
154 | return;
155 | }
156 | }
157 |
158 | sendPool.Return(sendMetrics);
159 | }
160 | else
161 | {
162 | receiveSessions.TryRemove(sessionId, out ReceiveMetrics receiveMetrics);
163 |
164 | lock (metricsLock)
165 | {
166 | if (rxMetricsBufferWriteIndex < this.activeReceiveMetricsBuffer.Length - 1)
167 | {
168 | rxMetricsBufferWriteIndex++;
169 |
170 | activeReceiveMetricsBuffer[rxMetricsBufferWriteIndex] = receiveMetrics;
171 | return;
172 | }
173 | }
174 |
175 | rxPool.Return(receiveMetrics);
176 | }
177 | }
178 |
179 | Guid ILogger.StartRecordTime()
180 | {
181 | var sessionId = Guid.NewGuid();
182 | if (isSendMetric)
183 | {
184 | var sendMetrics = sendPool.Get();
185 |
186 | if (sendMetrics != null)
187 | {
188 | sendSessions[sessionId] = sendMetrics;
189 | sendSessions[sessionId].Tick = sw.ElapsedTicks;
190 |
191 | return sessionId;
192 | }
193 | }
194 | else
195 | {
196 | var rxMetrics = rxPool.Get();
197 |
198 | if (rxMetrics != null)
199 | {
200 | receiveSessions[sessionId] = rxMetrics;
201 | receiveSessions[sessionId].Tick = sw.ElapsedTicks;
202 |
203 | return sessionId;
204 | }
205 | }
206 |
207 | return Guid.Empty;
208 | }
209 |
210 | double ILogger.StopRecordTimeAndGetElapsedMs(Guid sessionId)
211 | {
212 | if (sessionId == Guid.Empty)
213 | {
214 | return 0;
215 | }
216 |
217 | long elapsed;
218 | if (isSendMetric)
219 | {
220 | elapsed = sendSessions[sessionId].SendDuration100ns = sw.ElapsedTicks - sendSessions[sessionId].Tick;
221 | }
222 | else
223 | {
224 | elapsed = receiveSessions[sessionId].ReceiveDuration100ns = sw.ElapsedTicks - receiveSessions[sessionId].Tick;
225 | }
226 |
227 | return elapsed / (Stopwatch.Frequency / 1000.0);
228 | }
229 |
230 | void ILogger.StartCompletionRecordTime(Guid sessionId)
231 | {
232 | if (sessionId == Guid.Empty)
233 | {
234 | return;
235 | }
236 |
237 | if (!isSendMetric)
238 | {
239 | receiveSessions[sessionId].CompletionStartTick = sw.ElapsedTicks;
240 | }
241 | }
242 |
243 | double ILogger.StopCompletionRecordTimeAndGetElapsedMs(Guid sessionId)
244 | {
245 | if (sessionId == Guid.Empty || isSendMetric)
246 | {
247 | return 0;
248 | }
249 |
250 | receiveSessions[sessionId].CompleteDuration100ns = sw.ElapsedTicks - receiveSessions[sessionId].CompletionStartTick;
251 |
252 | return receiveSessions[sessionId].CompleteDuration100ns / (Stopwatch.Frequency / 1000.0);
253 | }
254 |
255 | void ILogger.IncrementActionCount(Guid sessionId)
256 | {
257 | if (sessionId == Guid.Empty)
258 | return;
259 |
260 | if (isSendMetric)
261 | {
262 | sendSessions[sessionId].Sends = sendSessions[sessionId].Sends + 1;
263 | }
264 | else
265 | {
266 | receiveSessions[sessionId].Receives = receiveSessions[sessionId].Receives + 1;
267 | }
268 | }
269 |
270 | void ILogger.IncrementMetricValueBy(Guid sessionId, int count)
271 | {
272 | if (sessionId == Guid.Empty)
273 | return;
274 |
275 | if (isSendMetric)
276 | {
277 | sendSessions[sessionId].Messages = sendSessions[sessionId].Messages + count;
278 | }
279 | else
280 | {
281 | receiveSessions[sessionId].Messages = receiveSessions[sessionId].Messages + count;
282 | }
283 | }
284 |
285 | void ILogger.IncrementErrorCount(Guid sessionId)
286 | {
287 | if (sessionId == Guid.Empty)
288 | return;
289 |
290 | if (isSendMetric)
291 | {
292 | sendSessions[sessionId].Errors = sendSessions[sessionId].Errors + 1;
293 | }
294 | else
295 | {
296 | receiveSessions[sessionId].Errors = receiveSessions[sessionId].Errors + 1;
297 | }
298 | }
299 |
300 | void ILogger.IncrementBusyErrorCount(Guid sessionId)
301 | {
302 | if (sessionId == Guid.Empty)
303 | return;
304 |
305 | if (isSendMetric)
306 | {
307 | sendSessions[sessionId].BusyErrors = sendSessions[sessionId].BusyErrors + 1;
308 | }
309 | else
310 | {
311 | receiveSessions[sessionId].BusyErrors = receiveSessions[sessionId].BusyErrors + 1;
312 | }
313 | }
314 |
315 | void ILogger.AddTrace(string trace)
316 | {
317 | }
318 |
319 | private void Dispose(bool disposing)
320 | {
321 | if (!disposedValue)
322 | {
323 | if (disposing)
324 | {
325 | this.timer.Enabled = false;
326 |
327 | lock (metricsLock)
328 | {
329 | this.timer.Enabled = false;
330 |
331 | this.sendSessions.Clear();
332 | this.receiveSessions.Clear();
333 | this.sendSessions = null;
334 | this.receiveSessions = null;
335 | }
336 | }
337 |
338 | disposedValue = true;
339 | }
340 | }
341 |
342 | public void Dispose()
343 | {
344 | Dispose(disposing: true);
345 | GC.SuppressFinalize(this);
346 | }
347 | }
348 |
349 | public class ObjectPool
350 | {
351 | private readonly ConcurrentBag _objects;
352 | private readonly Func _objectGenerator;
353 | private readonly Action _objectCleaner;
354 | private readonly long _maxCount;
355 | int _loaned = 0;
356 |
357 | public ObjectPool(Func objectGenerator, Action objectCleaner, long maxCount = 5000)
358 | {
359 | _objectGenerator = objectGenerator ?? throw new ArgumentNullException(nameof(objectGenerator));
360 | _objectCleaner = objectCleaner;
361 | _objects = new ConcurrentBag();
362 | _maxCount = maxCount;
363 | }
364 |
365 | public T Get()
366 | {
367 | if (this._loaned >= _maxCount)
368 | {
369 | return default;
370 | }
371 |
372 | Interlocked.Increment(ref this._loaned);
373 |
374 | if (_objects.TryTake(out T item))
375 | {
376 | return item;
377 | }
378 |
379 | return _objectGenerator();
380 | }
381 |
382 | public void Return(T item)
383 | {
384 | _objectCleaner?.Invoke(item);
385 |
386 | Interlocked.Decrement(ref this._loaned);
387 | _objects.Add(item);
388 | }
389 | }
390 |
391 |
392 | public class MetricsDisplayEventArgs : EventArgs
393 | {
394 | public ArraySegment SendMetricsList { get; set; }
395 | public ArraySegment ReceiveMetricsList { get; set; }
396 | }
397 | }
398 |
--------------------------------------------------------------------------------
/Results.txt:
--------------------------------------------------------------------------------
1 | //Here are the detailed results using a D4 V2 VM in North Europe.
2 |
3 | //Summary
4 | Scenario Send Rate (msg/s) Receive Rate (msg/s)
5 | 1MU_Queue 4401 4401
6 | 1MU_1Subscription 2223 2220
7 | 1MU_5Subscriptions 1348 2994
8 | 2MU_Queue 9070 9069
9 | 2MU_1Subscription 4499 4493
10 | 2MU_5Subscriptions 1676 8331
11 | 4MU_Queue 16782 16782
12 | 4MU_1Subscription 8960 8956
13 | 4MU_5Subscriptions 3844 19202
14 |
15 | //1MU_Queue
16 | Time Send Rate (msg/s) Send Latency (ms) Send Count (msg) Receive Rate (msg/s) Receive Latency (ms) Complete Latency (ms) Receive Count (msg) Server Busy Count Other Exceptions
17 | 8:22:43 PM 3,893 252.36 116,800 3,893 347.75 151.97 116,800 0 0
18 | 8:23:13 PM 4,313 231.14 129,400 4,293 339.47 126.96 128,801 0 0
19 | 8:23:43 PM 4,460 222.37 133,800 4,473 316.15 128.56 134,200 0 0
20 | 8:24:13 PM 4,726 211.17 141,800 4,720 318.57 108.21 141,600 0 0
21 | 8:24:43 PM 4,240 233.36 127,200 4,240 346.39 122.65 127,200 0 0
22 | 8:25:13 PM 4,410 226.37 132,300 4,400 341.29 113.83 132,000 0 0
23 | 8:25:43 PM 4,443 224.8 133,300 4,456 333.39 118.93 133,700 0 0
24 | 8:26:13 PM 4,420 224.7 132,600 4,426 333.55 117.49 132,800 0 0
25 | 8:26:43 PM 4,096 243.1 122,900 4,100 347.1 140.91 123,000 0 0
26 | 8:27:13 PM 4,406 224.05 132,200 4,403 336.65 114.78 132,100 0 0
27 | 8:27:43 PM 4,300 232.82 129,000 4,303 343.91 123.53 129,100 0 0
28 | 8:28:13 PM 4,606 214.48 138,200 4,590 313.45 119.42 137,700 0 0
29 | 8:28:43 PM 4,333 231.19 130,000 4,350 330.77 131.81 130,500 0 0
30 | 8:29:14 PM 4,640 212.87 139,200 4,636 322.09 106.86 139,100 0 0
31 | 8:29:44 PM 4,570 219.22 137,100 4,573 316.7 123.97 137,200 0 0
32 | 8:30:14 PM 4,673 210.11 140,200 4,670 307.06 116.53 140,100 0 0
33 | 8:30:44 PM 4,476 224.29 134,300 4,456 347.72 103.96 133,700 0 0
34 | 8:31:14 PM 4,256 231.93 127,700 4,280 338.29 126.86 128,400 0 0
35 | 8:31:44 PM 4,186 234.4 125,600 4,186 344.79 126.09 125,600 0 0
36 | 8:32:14 PM 4,573 223.28 137,200 4,563 334.46 114.18 136,900 0 0
37 | 8:32:44 PM 4,390 223.9 131,700 4,393 336.82 115.1 131,800 0 0
38 | SUMMARY 4,401 225.81 2,799,300 4,401 332.8 121.06 2,799,301 0 0
39 |
40 | //1MU_1Subscription
41 | Time Send Rate (msg/s) Send Latency (ms) Send Count (msg) Receive Rate (msg/s) Receive Latency (ms) Complete Latency (ms) Receive Count (msg) Server Busy Count Other Exceptions
42 | 8:44:46 PM 2,300 441.15 66,700 2,203 579.07 312.55 63,900 0 0
43 | 8:45:16 PM 2,123 470.55 63,700 2,086 664.07 301.71 62,601 0 0
44 | 8:45:46 PM 1,740 570.12 52,200 1,786 697.03 410.95 53,600 0 0
45 | 8:46:16 PM 1,980 508.73 59,400 1,943 722.51 307.86 58,300 0 0
46 | 8:46:46 PM 2,166 456.2 65,000 2,173 596.04 310.18 65,200 0 0
47 | 8:47:16 PM 2,150 463.54 64,500 2,116 609.43 339.34 63,500 0 0
48 | 8:47:46 PM 2,280 441.89 68,400 2,353 595.79 269.9 70,600 0 0
49 | 8:48:16 PM 2,433 406.75 73,000 2,426 551.85 264.73 72,800 0 0
50 | 8:48:46 PM 2,530 397.2 75,900 2,490 601.71 210.33 74,700 0 0
51 | 8:49:16 PM 2,253 437.13 67,600 2,310 622.57 242.2 69,300 0 0
52 | 8:49:46 PM 2,260 444.87 67,800 2,240 625.95 275.18 67,200 0 0
53 | 8:50:16 PM 2,240 440.64 67,200 2,223 608.56 283.09 66,700 0 0
54 | 8:50:46 PM 2,303 437.01 69,100 2,313 615.14 253.72 69,400 0 0
55 | 8:51:16 PM 2,190 449.45 65,700 2,226 571.78 321.29 66,800 0 0
56 | 8:51:46 PM 2,360 426.69 70,800 2,343 592.48 265.74 70,300 0 0
57 | 8:52:16 PM 2,156 457.32 64,700 2,176 654.37 258.51 65,300 0 0
58 | 8:52:46 PM 2,176 461.89 65,300 2,096 685.74 274.34 62,900 0 0
59 | 8:53:16 PM 2,213 453.22 66,400 2,213 649.8 255.68 66,400 0 0
60 | 8:53:46 PM 2,350 418.12 70,500 2,386 606.89 224.08 71,600 0 0
61 | 8:54:16 PM 2,310 433.58 69,300 2,300 603.49 261.65 69,000 0 0
62 | SUMMARY 2,223 447.73 1,349,900 2,220 620.5 278.22 1,347,801 0 0
63 |
64 | //1MU_5Subscriptions
65 | Time Send Rate (msg/s) Send Latency (ms) Send Count (msg) Receive Rate (msg/s) Receive Latency (ms) Complete Latency (ms) Receive Count (msg) Server Busy Count Other Exceptions
66 | 9:00:32 PM 1,476 661.77 44,300 3,360 2125.05 485.8 100,800 0 0
67 | 9:01:02 PM 1,113 887.87 33,400 3,573 2191.07 579.58 107,203 0 0
68 | 9:01:32 PM 1,276 775.87 38,300 3,513 2113.54 721.09 105,402 0 0
69 | 9:02:02 PM 1,413 720.04 42,400 3,986 1961.2 594 119,600 0 0
70 | 9:02:32 PM 1,290 773.71 38,700 3,900 2057.51 528.96 117,000 0 0
71 | 9:03:02 PM 966 1025.73 29,000 3,300 2299.03 740.32 99,000 0 0
72 | 9:03:32 PM 996 991.5 29,900 3,163 2317.46 763.06 94,900 0 0
73 | 9:04:02 PM 1,250 797.75 37,500 3,836 2031.46 619.22 115,100 0 0
74 | 9:04:32 PM 1,330 754.7 39,900 4,100 1880.07 598.58 123,000 0 0
75 | 9:05:02 PM 1,203 826.99 36,100 3,750 2025.44 580.14 112,500 0 0
76 | 9:05:32 PM 1,203 820.18 36,100 3,450 2191.53 634.05 103,500 0 0
77 | 9:06:02 PM 1,043 961.26 31,300 3,433 2425.85 595.13 103,000 0 0
78 | 9:06:32 PM 1,196 822.16 35,900 3,616 2135.4 664.64 108,500 0 0
79 | 9:07:02 PM 1,043 964.81 31,300 2,700 2618.39 731.77 81,000 0 0
80 | 9:07:32 PM 1,190 771.12 35,700 3,450 2394.99 547.67 103,500 0 0
81 | 9:08:02 PM 1,603 621.52 48,100 1,800 5077.68 375.12 54,000 0 0
82 | 9:08:32 PM 2,216 490.21 66,500 1,050 8881.22 561.24 31,500 0 0
83 | 9:09:02 PM 1,343 725.58 40,300 2,100 4471.54 515.07 63,000 0 0
84 | 9:09:32 PM 2,076 321.96 62,300 900 9134.97 263.62 27,000 4 0
85 | 9:10:02 PM 1,436 779.45 43,100 1,950 5500.23 470.82 58,500 0 0
86 | SUMMARY 1,348 730.68 837,300 2,994 2718.07 596.58 1,859,505 4 0
87 |
88 | //2MU_Queue
89 | Time Send Rate (msg/s) Send Latency (ms) Send Count (msg) Receive Rate (msg/s) Receive Latency (ms) Complete Latency (ms) Receive Count (msg) Server Busy Count Other Exceptions
90 | 9:28:12 PM 9,103 111.07 264,000 9,089 171.91 50.96 263,600 0 0
91 | 9:28:42 PM 9,090 109.75 272,700 9,086 166.44 55.49 272,601 0 0
92 | 9:29:12 PM 9,133 108.32 274,000 9,140 164.41 54.59 274,200 0 0
93 | 9:29:42 PM 9,163 108.08 274,900 9,160 164.98 53.43 274,800 0 0
94 | 9:30:12 PM 9,046 109.32 271,400 9,043 166.68 53.17 271,300 0 0
95 | 9:30:42 PM 8,790 112.76 263,700 8,780 172.36 56.62 263,400 0 0
96 | 9:31:12 PM 9,503 104.09 285,100 9,520 157.42 52.97 285,600 0 0
97 | 9:31:42 PM 9,186 107.74 275,600 9,183 164.08 53.61 275,500 0 0
98 | 9:32:12 PM 9,503 104.08 285,100 9,510 160.32 50.08 285,300 0 0
99 | 9:32:43 PM 9,393 104.37 281,800 9,396 160.49 50.37 281,900 0 0
100 | 9:33:13 PM 8,803 113.69 264,100 8,793 172.08 57.54 263,800 0 0
101 | 9:33:43 PM 8,723 113.1 261,700 8,730 167.69 61 261,900 0 0
102 | 9:34:13 PM 8,760 113.33 262,800 8,746 172.98 55.64 262,400 0 0
103 | 9:34:43 PM 9,396 105.22 281,900 9,406 157.33 55.13 282,200 0 0
104 | 9:35:13 PM 8,563 115.97 256,900 8,560 172.38 61.49 256,800 0 0
105 | 9:35:43 PM 9,473 104.49 284,200 9,476 158.04 53.65 284,300 0 0
106 | 9:36:13 PM 9,093 108.85 272,800 9,100 164.78 54.8 273,000 0 0
107 | 9:36:43 PM 8,956 110.64 268,700 8,950 164.21 59.29 268,500 0 0
108 | 9:37:13 PM 9,050 108.74 271,500 9,046 161.84 57.86 271,400 0 0
109 | 9:37:43 PM 9,193 108.21 275,800 9,206 164.38 54.02 276,200 0 0
110 | 9:38:13 PM 8,546 115.12 256,400 8,546 174.19 58.52 256,400 0 0
111 | SUMMARY 9,070 109.21 5,868,300 9,069 165.56 55.02 5,868,101 0 0
112 |
113 | //2MU_1Subscription
114 | Time Send Rate (msg/s) Send Latency (ms) Send Count (msg) Receive Rate (msg/s) Receive Latency (ms) Complete Latency (ms) Receive Count (msg) Server Busy Count Other Exceptions
115 | 9:41:25 PM 5,020 204.17 145,600 4,910 278.58 132.67 142,400 0 0
116 | 9:41:55 PM 4,640 213.75 139,200 4,670 300.04 131 140,101 0 0
117 | 9:42:25 PM 4,480 222.34 134,400 4,496 293.44 151.81 134,900 0 0
118 | 9:42:55 PM 4,772 214.63 138,400 4,775 291.15 140.61 138,500 0 0
119 | 9:43:25 PM 4,323 230.59 129,700 4,300 335.89 127.41 129,000 0 0
120 | 9:43:55 PM 4,263 230.69 127,900 4,240 315.01 154.78 127,200 0 0
121 | 9:44:25 PM 4,416 227.63 132,500 4,406 309.53 145.14 132,200 0 0
122 | 9:44:55 PM 4,070 241.74 122,100 4,076 336.27 153.77 122,300 0 0
123 | 9:45:25 PM 4,260 235.85 127,800 4,286 316.46 154.29 128,600 0 0
124 | 9:45:55 PM 4,183 235.28 125,500 4,180 306.46 167.77 125,400 0 0
125 | 9:46:25 PM 4,156 241.81 124,700 4,170 317.27 167.6 125,100 0 0
126 | 9:46:55 PM 4,350 225.91 130,500 4,336 325.03 130.29 130,100 0 0
127 | 9:47:25 PM 4,640 215.82 139,200 4,620 291.68 144.78 138,600 0 0
128 | 9:47:55 PM 4,890 203.97 146,700 4,893 261.97 143.72 146,800 0 0
129 | 9:48:25 PM 4,463 221.46 133,900 4,476 301.91 147.75 134,300 0 0
130 | 9:48:55 PM 4,590 217.69 137,700 4,596 302.68 135.51 137,900 0 0
131 | 9:49:25 PM 4,746 207.64 142,400 4,780 273.49 141.9 143,400 0 0
132 | 9:49:55 PM 4,876 205.37 146,300 4,803 282.36 134.93 144,100 0 0
133 | 9:50:25 PM 4,343 226.86 130,300 4,433 301.51 148.48 133,000 0 0
134 | 9:50:55 PM 4,763 210.66 142,900 4,720 281.47 144.64 141,600 0 0
135 | SUMMARY 4,499 221.06 2,780,600 4,493 300.43 144.54 2,777,201 0 0
136 |
137 | //2MU_5Subscriptions
138 | Time Send Rate (msg/s) Send Latency (ms) Send Count (msg) Receive Rate (msg/s) Receive Latency (ms) Complete Latency (ms) Receive Count (msg) Server Busy Count Other Exceptions
139 | 9:54:15 PM 1,743 562.37 52,300 8,166 728.15 436.19 245,000 0 0
140 | 9:54:45 PM 1,653 605.53 49,600 8,083 787.49 453.43 242,505 0 0
141 | 9:55:15 PM 1,556 625.7 46,700 8,060 738.04 488.65 241,800 0 0
142 | 9:55:45 PM 1,730 587.05 51,900 8,606 725.39 451.44 258,200 0 0
143 | 9:56:15 PM 1,616 608.64 48,500 7,683 800.74 479.05 230,500 0 0
144 | 9:56:45 PM 1,653 607.15 49,600 8,280 738.76 474.09 248,400 0 0
145 | 9:57:15 PM 1,836 539.63 55,100 7,670 970.61 316.49 230,100 0 0
146 | 9:57:45 PM 1,783 557.96 53,500 8,646 762.54 413.66 259,400 0 0
147 | 9:58:15 PM 1,773 562.97 53,200 8,803 729.78 403.42 264,100 0 0
148 | 9:58:45 PM 1,753 558.05 52,600 8,920 691.91 434.67 267,600 0 0
149 | 9:59:15 PM 1,846 542.69 55,400 9,346 676.24 400.94 280,400 0 0
150 | 9:59:45 PM 1,816 546.97 54,500 8,850 692.61 424.73 265,500 0 0
151 | 12:00:15 PM 1,760 567.43 52,800 8,826 778.32 362.28 264,800 0 0
152 | 12:00:45 PM 1,763 558.89 52,900 8,953 710.34 400.85 268,600 0 0
153 | 12:01:15 PM 1,773 564.84 53,200 8,720 681.64 479.79 261,600 0 0
154 | 12:01:45 PM 1,673 588.69 50,200 8,216 774.92 430.76 246,500 0 0
155 | 12:02:15 PM 1,470 659.45 44,100 7,090 823.23 558.85 212,700 0 0
156 | 12:02:45 PM 1,493 683.14 44,800 8,743 621.6 545.49 262,300 0 0
157 | 12:03:15 PM 1,582 645.18 45,900 8,793 691.52 487.47 255,000 0 0
158 | 12:03:45 PM 1,546 651.21 46,400 7,793 728.53 546.19 233,800 0 0
159 | 12:04:15 PM 1,570 633.17 47,100 7,826 815.24 469.88 234,800 0 0
160 | SUMMARY 1,676 593.04 1,104,900 8,331 749.38 449.07 5,490,705 0 0
161 |
162 | //4MU_Queue
163 | Time Send Rate (msg/s) Send Latency (ms) Send Count (msg) Receive Rate (msg/s) Receive Latency (ms) Complete Latency (ms) Receive Count (msg) Server Busy Count Other Exceptions
164 | 10:09:47 PM 16,848 119.57 488,600 16,827 177.34 66.46 488,000 0 0
165 | 10:10:17 PM 17,316 111.32 519,500 17,336 165.01 62.6 520,101 0 0
166 | 10:10:47 PM 16,313 122.31 489,400 16,293 180.32 68.5 488,800 0 0
167 | 10:11:17 PM 16,773 116.81 503,200 16,756 173.4 65.31 502,700 0 0
168 | 10:11:47 PM 16,400 119.42 492,000 16,420 176.48 66.99 492,600 0 0
169 | 10:12:17 PM 16,440 119.13 493,200 16,460 179.39 63.77 493,800 0 0
170 | 10:12:47 PM 17,330 113.13 519,900 17,316 169.21 61.54 519,500 0 0
171 | 10:13:17 PM 17,243 113.28 517,300 17,246 170.26 61.26 517,400 0 0
172 | 10:13:47 PM 16,393 119.88 491,800 16,383 176.52 68.07 491,500 0 0
173 | 10:14:17 PM 16,713 117.01 501,400 16,726 174.85 63.73 501,800 0 0
174 | 10:14:47 PM 16,293 120.93 488,800 16,246 177.72 68.55 487,400 0 0
175 | 10:15:17 PM 15,433 126.99 463,000 15,453 186.8 71.38 463,600 0 0
176 | 10:15:47 PM 17,400 112.94 522,000 17,396 166.77 64.36 521,900 0 0
177 | 10:16:17 PM 16,550 118.49 496,500 16,566 175.73 65.55 497,000 0 0
178 | 10:16:47 PM 17,913 109.39 537,400 17,920 164.84 58.46 537,600 0 0
179 | 10:17:17 PM 17,550 111.78 526,500 17,550 166.61 61.29 526,500 0 0
180 | 10:17:47 PM 16,656 117.89 499,700 16,650 174.45 65.71 499,500 0 0
181 | 10:18:17 PM 17,613 111.46 528,400 17,603 166.14 61.1 528,100 0 0
182 | 10:18:47 PM 16,840 116.31 505,200 16,840 173.54 63.71 505,200 0 0
183 | 10:19:17 PM 15,960 123.18 478,800 15,953 186.06 64.59 478,600 0 0
184 | 10:19:47 PM 16,833 116.51 505,000 16,860 173.03 64.16 505,800 0 0
185 | SUMMARY 16,782 116.89 10,572,700 16,782 173.82 64.54 10,572,701 0 0
186 |
187 | //4MU_1Subscription
188 | Time Send Rate (msg/s) Send Latency (ms) Send Count (msg) Receive Rate (msg/s) Receive Latency (ms) Complete Latency (ms) Receive Count (msg) Server Busy Count Other Exceptions
189 | 10:21:32 PM 9,553 103.07 286,600 9,480 153.12 55.41 284,400 0 0
190 | 10:22:02 PM 9,210 107.7 276,300 9,230 156.91 59.92 276,901 0 0
191 | 10:22:32 PM 8,626 114.13 258,800 8,620 169.03 62.22 258,600 0 0
192 | 10:23:02 PM 9,223 107.43 276,700 9,190 160.78 57.26 275,700 0 0
193 | 10:23:32 PM 8,880 111.5 266,400 8,920 165.09 59.42 267,600 0 0
194 | 10:24:02 PM 9,230 107.02 276,900 9,236 160.08 56.61 277,100 0 0
195 | 10:24:32 PM 9,460 104.5 283,800 9,390 155.73 55.98 281,700 0 0
196 | 10:25:02 PM 9,556 103.47 286,700 9,606 154.66 54.99 288,200 0 0
197 | 10:25:32 PM 8,753 112.27 262,600 8,760 166.4 60.55 262,800 0 0
198 | 10:26:02 PM 8,573 116.23 257,200 8,580 171.8 62.85 257,400 0 0
199 | 10:26:32 PM 8,600 115.02 258,000 8,603 169.9 61.79 258,100 0 0
200 | 10:27:02 PM 8,256 119.59 247,700 8,240 177.09 65.93 247,200 0 0
201 | 10:27:32 PM 8,950 110.87 268,500 8,973 163.45 59.87 269,200 0 0
202 | 10:28:02 PM 9,103 106.07 273,100 9,100 156.25 57.69 273,000 0 0
203 | 10:28:32 PM 8,333 121.46 250,000 8,300 176.68 70.24 249,000 0 0
204 | 10:29:02 PM 8,563 114.95 256,900 8,633 168.6 62.92 259,000 0 0
205 | 10:29:32 PM 9,043 109.85 271,300 9,020 164.45 57.86 270,600 0 0
206 | 10:30:02 PM 9,216 107.46 276,500 9,206 161.22 56.07 276,200 0 0
207 | 10:30:32 PM 9,043 109.45 271,300 9,026 162.05 59.7 270,800 0 0
208 | 10:31:02 PM 8,740 113.14 262,200 8,726 164.09 64.49 261,800 0 0
209 | SUMMARY 8,960 110.48 5,394,100 8,956 163.5 59.91 5,391,901 0 0
210 |
211 | //4MU_5Subscriptions
212 | Time Send Rate (msg/s) Send Latency (ms) Send Count (msg) Receive Rate (msg/s) Receive Latency (ms) Complete Latency (ms) Receive Count (msg) Server Busy Count Other Exceptions
213 | 10:31:49 PM 4,036 243.05 121,100 19,816 300.78 189.76 594,500 0 0
214 | 10:32:19 PM 3,873 252.31 116,200 19,140 337.19 183.74 574,205 0 0
215 | 10:32:49 PM 3,850 257.91 115,500 19,376 330.57 187.87 581,300 0 0
216 | 10:33:19 PM 3,866 252.7 116,000 19,340 335.22 178.4 580,200 0 0
217 | 10:33:49 PM 3,643 268.62 109,300 18,293 340.67 199.84 548,800 0 0
218 | 10:34:19 PM 3,750 267.45 112,500 18,656 356.47 190.49 559,700 0 0
219 | 10:34:49 PM 3,930 251.07 117,900 19,660 326.91 182.26 589,800 0 0
220 | 10:35:19 PM 4,010 246.95 120,300 20,136 329.46 167.61 604,100 0 0
221 | 10:35:50 PM 3,890 252.73 116,700 19,453 331.75 181.69 583,600 0 0
222 | 10:36:20 PM 4,013 246.36 120,400 19,990 324.79 177.29 599,700 0 0
223 | 10:36:50 PM 3,856 252.98 115,700 19,410 318.69 193.77 582,300 0 0
224 | 10:37:20 PM 3,746 265.39 112,400 18,606 323.13 215.6 558,200 0 0
225 | 10:37:50 PM 3,986 256.55 115,600 19,720 326.35 195.59 571,900 0 0
226 | 10:38:20 PM 3,840 257.13 115,200 19,520 332.81 183.87 585,600 0 0
227 | 10:38:50 PM 3,850 253.67 115,500 19,200 337.96 177.59 576,000 0 0
228 | 10:39:20 PM 3,876 255.4 116,300 19,253 337.77 181.8 577,600 0 0
229 | 10:39:50 PM 3,973 249.5 119,200 20,006 332.81 171.43 600,200 0 0
230 | 10:40:20 PM 3,623 272.15 108,700 17,940 344.98 207.73 538,200 0 0
231 | 10:40:50 PM 3,836 255.86 115,100 19,356 327.38 191.38 580,700 0 0
232 | 10:41:20 PM 3,646 270.88 109,400 18,280 340.33 202.58 548,400 0 0
233 | 10:41:50 PM 3,680 271.5 110,400 18,233 364.19 188.82 547,000 0 0
234 | 10:42:20 PM 3,823 256.43 114,700 19,243 337.29 180.31 577,300 0 0
235 | SUMMARY 3,844 256.86 2,545,100 19,202 333.43 187.4 12,711,805 0 0
236 |
237 |
--------------------------------------------------------------------------------
/ThroughputTest_v2/ServiceBusThroughputTest/Program.cs:
--------------------------------------------------------------------------------
1 | //---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
5 | // EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
6 | // OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
7 | //---------------------------------------------------------------------------------
8 |
9 | namespace ServiceBusThroughputTest
10 | {
11 | using System;
12 | using System.Collections.Generic;
13 | using System.Linq;
14 | using System.Threading;
15 | using System.Threading.Tasks;
16 | using CommandLine;
17 | using CommandLine.Text;
18 | using System.Diagnostics;
19 | using ServiceBusThroughputTestLib;
20 | using System.Configuration;
21 |
22 | class Program
23 | {
24 | const string connString = "";
25 | const string queue = "queue1";
26 | const string MetricsSamplesPerDisplayIntervalSenderKeyName = "MetricsSamplesPerDisplayIntervalSender";
27 | const string MetricsSamplesPerDisplayIntervalReceiverKeyName = "MetricsSamplesPerDisplayIntervalReceiver";
28 | const int MetricsSamplesPerDisplayIntervalSenderDefault = 2000;
29 | const int MetricsSamplesPerDisplayIntervalReceiverDefault = 10000;
30 |
31 | static void Main(string[] args)
32 | {
33 | CommandLine.Parser.Default.ParseArguments(args)
34 | .WithParsed(opts => StartSendReceives(opts));
35 | }
36 |
37 | static void StartSendReceives(Settings settings)
38 | {
39 | CancellationTokenSource cts = new CancellationTokenSource();
40 |
41 | var cancelTask = Task.Run(() =>
42 | {
43 | Console.ReadLine();
44 | cts.Cancel();
45 | });
46 |
47 | settings.PrintSettings();
48 |
49 | int metricsSamplesPerDisplayIntervalSender, metricsSamplesPerDisplayIntervalReceiver;
50 | GetConfigValue(MetricsSamplesPerDisplayIntervalSenderKeyName, int.Parse, out metricsSamplesPerDisplayIntervalSender, MetricsSamplesPerDisplayIntervalSenderDefault);
51 | GetConfigValue(MetricsSamplesPerDisplayIntervalReceiverKeyName, int.Parse, out metricsSamplesPerDisplayIntervalReceiver, MetricsSamplesPerDisplayIntervalReceiverDefault);
52 |
53 | Console.WriteLine("Starting senders and receivers. Press Enter key to stop at anytime...");
54 |
55 | if (settings.SenderCount > 0)
56 | {
57 | Console.ForegroundColor = ConsoleColor.Yellow;
58 |
59 | Console.Write("S|{0,10:0.00}|{1,10:0.00}|{2,5}|{3,5}|", "pstart", "pend", "sbc", "mifs");
60 | Console.Write("{0,10:0.00}|{1,10:0.00}|{2,10:0.00}|{3,10:0.00}|", "snd.avg", "snd.min", "snd.max", "snd.p99");
61 | Console.WriteLine("{0,10}|{1,10}|{2,10}|{3,10}|{4,10}|{5,10}|", "msg/s", "total", "sndop", "errs", "busy", "overall");
62 | }
63 |
64 | if (settings.ReceiverCount > 0)
65 | {
66 | Console.ForegroundColor = ConsoleColor.Cyan;
67 |
68 | Console.Write("R|{0,10:0.00}|{1,10:0.00}|{2,5}|{3,5}|", "pstart", "pend", "rbc", "mifr");
69 | Console.Write("{0,10:0.00}|{1,10:0.00}|{2,10:0.00}|{3,10:0.00}|", "rcv.avg", "rcv.min", "rcv.max", "rcv.p99");
70 |
71 | Console.Write("{0,10}|{1,10}|{2,10}|{3,10}|{4,10}|{5,10}|", "msg/s", "total", "rcvop", "errs", "busy", "overall");
72 | Console.WriteLine("{0,10:0.00}|{1,10:0.00}|{2,10:0.00}|{3,10:0.00}|", "cpl.avg", "cpl.min", "cpl.max", "cpl.p99");
73 | }
74 |
75 | Console.ForegroundColor = ConsoleColor.White;
76 |
77 | var sndRxTasks = new List { cancelTask };
78 | long sendTotal = 0, receiveTotal = 0;
79 |
80 | if (settings.ReceiverCount > 0)
81 | {
82 | Metrics receiveMetrics = new Metrics(cts.Token, false, settings.MetricsDisplayFrequency, metricsSamplesPerDisplayIntervalReceiver);
83 |
84 | Receiver receiver = new Receiver(settings.ConnectionString, settings.QueueName, settings.PrefetchCount, settings.ReceiveBatchSize, settings.MaxInflightReceives, settings.ReceiverCount, settings.ReceiveCallIntervalMs, !settings.IsPeekLock, receiveMetrics);
85 | receiveMetrics.OnMetricsDisplay += (s, e) => Metrics_OnMetricsDisplay(s, e, settings, ref sendTotal, ref receiveTotal);
86 |
87 | sndRxTasks.Add(receiver.Run(cts.Token));
88 | }
89 |
90 | if (settings.SenderCount > 0)
91 | {
92 | Metrics sendMetrics = new Metrics(cts.Token, true, settings.MetricsDisplayFrequency, metricsSamplesPerDisplayIntervalSender);
93 | Sender sender = new Sender(settings.ConnectionString, settings.QueueName, settings.PayloadSizeInBytes, settings.SendBatchSize, settings.SendCallIntervalMs, settings.MaxInflightSends, settings.SenderCount, sendMetrics);
94 |
95 | sendMetrics.OnMetricsDisplay += (s, e) => Metrics_OnMetricsDisplay(s, e, settings, ref sendTotal, ref receiveTotal);
96 |
97 | sndRxTasks.Add(sender.Run(cts.Token));
98 | }
99 |
100 | Task.WhenAny(sndRxTasks).Wait();
101 | }
102 |
103 | private static void Metrics_OnMetricsDisplay(object _, MetricsDisplayEventArgs e, Settings settings, ref long sendTotal, ref long receiveTotal)
104 | {
105 | var list = e.SendMetricsList;
106 | if (list.Count > 0)
107 | {
108 | lock (Console.Out)
109 | {
110 | Console.ForegroundColor = ConsoleColor.Yellow;
111 | Console.Write("S|{0,10}|{1,10}|{2,5}|{3,5}|", list.First().Tick / Stopwatch.Frequency + 1, list.Last().Tick / Stopwatch.Frequency + 1, settings.SendBatchSize, settings.MaxInflightSends);
112 |
113 | WriteStat(list, i => i.SendDuration100ns, Stopwatch.Frequency / 1000.0);
114 | var msgs = list.Sum(i => i.Messages);
115 | sendTotal += msgs;
116 |
117 | Console.WriteLine("{0,10:0.00}|{1,10}|{2,10}|{3,10}|{4,10}|{5,10}|", list.Sum(i => i.Messages) / (double)settings.MetricsDisplayFrequency, msgs, list.Sum(i => i.Sends), list.Sum(i => i.Errors), list.Sum(i => i.BusyErrors), sendTotal);
118 | }
119 | }
120 |
121 | var rList = e.ReceiveMetricsList;
122 | if (rList.Count > 0)
123 | {
124 | lock (Console.Out)
125 | {
126 | Console.ForegroundColor = ConsoleColor.Cyan;
127 |
128 | Console.Write("R|{0,10}|{1,10}|{2,5}|{3,5}|", rList.First().Tick / Stopwatch.Frequency + 1, rList.Last().Tick / Stopwatch.Frequency + 1, settings.ReceiveBatchSize, settings.MaxInflightReceives);
129 | WriteStat(rList, i => i.ReceiveDuration100ns, Stopwatch.Frequency / 1000.0);
130 |
131 | var msgs = rList.Sum(i => i.Messages);
132 | receiveTotal += msgs;
133 | Console.Write("{0,10:0.00}|{1,10}|{2,10}|{3,10}|{4,10}|{5,10}|", rList.Sum(i => i.Messages) / (double)settings.MetricsDisplayFrequency, msgs, rList.Sum(i => i.Receives), rList.Sum(i => i.Errors), rList.Sum(i => i.BusyErrors), receiveTotal);
134 |
135 | WriteStat(rList, i => i.CompleteDuration100ns, Stopwatch.Frequency / 1000.0);
136 | Console.WriteLine();
137 | }
138 | }
139 |
140 | Console.ForegroundColor = ConsoleColor.White;
141 | }
142 |
143 | public static double Percentile(long[] elements, double percentile)
144 | {
145 | Array.Sort(elements);
146 | double realIndex = percentile * (elements.Length - 1);
147 | int index = (int)realIndex;
148 | double frac = realIndex - index;
149 | if (index + 1 < elements.Length)
150 | return elements[index] * (1 - frac) + elements[index + 1] * frac;
151 | else
152 | return elements[index];
153 | }
154 |
155 | static void WriteStat(IList list, Func f, double scale)
156 | {
157 | Console.Write("{0,10:0.00}|{1,10:0.00}|{2,10:0.00}|{3,10:0.00}|", list.Average(f) / scale, list.Min(f) / scale, list.Max(f) / scale, Percentile(list.Select(s => f(s)).ToArray(), 0.99) / scale);
158 | }
159 |
160 | static bool GetConfigValue(string keyName, Func parseFunc, out T val, T defaultVal = default(T))
161 | {
162 | if (ConfigurationManager.AppSettings.AllKeys.Contains(keyName))
163 | {
164 | try
165 | {
166 | val = parseFunc(ConfigurationManager.AppSettings[keyName]);
167 |
168 | return true;
169 | }
170 | catch { }
171 | }
172 |
173 | val = defaultVal;
174 |
175 | return false;
176 | }
177 |
178 | static void TestSBReceiver()
179 | {
180 | ServiceBusThroughputTestLib.Receiver receiver = new ServiceBusThroughputTestLib.Receiver(connString, "queue1", 1024*10, 10, 1024, 1, 60000, false);
181 | CancellationTokenSource cts = new CancellationTokenSource();
182 | Task rt = receiver.Run(cts.Token);
183 | Thread.Sleep(TimeSpan.FromMinutes(2));
184 | cts.Cancel();
185 | rt.Wait();
186 | }
187 |
188 | static void TestSBSender()
189 | {
190 | int batchSize = 8;
191 | for (int i = 1; i < 2; i++)
192 | {
193 | Console.WriteLine("**** batchSize: " + batchSize);
194 | ServiceBusThroughputTestLib.Sender sender = new ServiceBusThroughputTestLib.Sender(connString, "queue1", 600, 25, batchSize, 1, 1);
195 | CancellationTokenSource cts = new CancellationTokenSource();
196 | Task st = sender.Run(cts.Token);
197 | Thread.Sleep(TimeSpan.FromMinutes(5));
198 | //cts.Cancel();
199 | st.Wait();
200 | batchSize *= 2;
201 | }
202 | }
203 |
204 | static void TestSBSenderLargeMsg()
205 | {
206 | int batchSize = 256;
207 | for (int i = 9; i > 0; i--)
208 | {
209 | Console.WriteLine("**** batchSize: " + batchSize);
210 | ServiceBusThroughputTestLib.Sender ehSender = new ServiceBusThroughputTestLib.Sender(connString, "queue1", 25 * 1024, 25, batchSize, 1, 1);
211 | CancellationTokenSource cts = new CancellationTokenSource();
212 | Task st = ehSender.Run(cts.Token);
213 | Thread.Sleep(TimeSpan.FromMinutes(5));
214 | cts.Cancel();
215 | st.Wait();
216 | batchSize /= 2;
217 | }
218 | }
219 | }
220 |
221 | class Settings
222 | {
223 | [Option('C', "connection-string", Required = true, HelpText = "Connection string")]
224 | public string ConnectionString { get; set; }
225 |
226 | [Option('S', "entity-path", Required = false, HelpText = "Entity path. Queue or topic. For Topic/Subscription, provide in this format - {topic}:{subscription}")]
227 | public string QueueName { get; set; }
228 |
229 | [Option('b', "payload-size-bytes", Required = false, HelpText = "Bytes per message (default 1024)")]
230 | public int PayloadSizeInBytes { get; set; } = 1024;
231 |
232 | [Option('f', "frequency-metrics", Required = false, HelpText = "Frequency of metrics display (seconds, default 10s)")]
233 | public int MetricsDisplayFrequency { get; set; } = 10;
234 |
235 | [Option('m', "receive-mode", Required = false, HelpText = "Receive mode. 'true' for PeekLock (default) or 'false' for ReceiveAndDelete")]
236 | public bool IsPeekLock { get; set; } = true;
237 |
238 | [Option('r', "receiver-count", Required = false, HelpText = "Number of concurrent receivers (default 1)")]
239 | public int ReceiverCount { get; set; } = 5;
240 |
241 | [Option('p', "prefetch-count", Required = false, HelpText = "Prefetch count (default 0)")]
242 | public int PrefetchCount { get; set; } = 100;
243 |
244 | [Option('t', "send-batch-size", Required = false, HelpText = "Number of messages per batch (default 0, no batching)")]
245 | public int SendBatchSize { get; set; } = 1;
246 |
247 | [Option('s', "sender-count", Required = false, HelpText = "Number of concurrent senders (default 1)")]
248 | public int SenderCount { get; set; } = 1;
249 |
250 | [Option('d', "send-callIntervalMs", Required = false, HelpText = "Delay between sends of any sender (milliseconds, default 0)")]
251 | public int SendCallIntervalMs { get; private set; } = 1000;
252 |
253 | [Option('e', "receive-callIntervalMs", Required = false, HelpText = "Receive timeout for receiver (milliseconds, default 0)")]
254 | public int ReceiveCallIntervalMs { get; private set; } = 5000;
255 |
256 | [Option('i', "inflight-sends", Required = false, HelpText = "Maximum numbers of concurrent in-flight send operations (default 1)")]
257 | public int MaxInflightSends { get; internal set; } = 1;
258 |
259 | [Option('j', "inflight-receives", Required = false, HelpText = "Maximum number of concurrent in-flight receive operations per receiver (default 1)")]
260 | public int MaxInflightReceives { get; internal set; } = 1;
261 |
262 | [Option('v', "receive-batch-size", Required = false, HelpText = "Max number of messages per batch (default 0, no batching)")]
263 | public int ReceiveBatchSize { get; private set; } = 1;
264 |
265 | public void PrintSettings()
266 | {
267 | Console.WriteLine("Settings:");
268 | Console.WriteLine("{0}: {1}", "SendPaths", this.QueueName);
269 | Console.WriteLine("{0}: {1}", "PayloadSizeInBytes", this.PayloadSizeInBytes);
270 | Console.WriteLine("{0}: {1}", "SenderCount", this.SenderCount);
271 | Console.WriteLine("{0}: {1}", "SendBatchCount", this.SendBatchSize);
272 | Console.WriteLine("{0}: {1}", "SendDelay", this.SendCallIntervalMs);
273 | Console.WriteLine("{0}: {1}", "MaxInflightSends", this.MaxInflightSends);
274 | Console.WriteLine("{0}: {1}", "IsPeekLock", this.IsPeekLock);
275 | Console.WriteLine("{0}: {1}", "ReceiverCount", this.ReceiverCount);
276 | Console.WriteLine("{0}: {1}", "ReceiveBatchCount", this.ReceiveBatchSize);
277 | Console.WriteLine("{0}: {1}", "PrefetchCount", this.PrefetchCount);
278 | Console.WriteLine("{0}: {1}", "ReceiveTimeout", this.ReceiveCallIntervalMs);
279 | Console.WriteLine("{0}: {1}", "MaxInflightReceives", this.MaxInflightReceives);
280 | Console.WriteLine("{0}: {1}", "MetricsDisplayFrequency", this.MetricsDisplayFrequency);
281 |
282 | Console.WriteLine();
283 | }
284 |
285 | [Usage()]
286 | public static IEnumerable Examples
287 | {
288 | get
289 | {
290 | yield return new Example("queue scenario", new Settings { ConnectionString = "{Connection-String}", QueueName = "{Queue-Name}" });
291 | yield return new Example("topic scenario", new Settings { ConnectionString = "{Connection-String}", QueueName = "{Topic-Name}:{Subsription-Name}"});
292 | }
293 | }
294 | }
295 | }
--------------------------------------------------------------------------------
/ThroughputTest/ReceiverTask.cs:
--------------------------------------------------------------------------------
1 | //---------------------------------------------------------------------------------
2 | // Copyright (c) Microsoft Corporation. All rights reserved.
3 | //
4 | // THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
5 | // EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
6 | // OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
7 | //---------------------------------------------------------------------------------
8 |
9 | namespace ThroughputTest
10 | {
11 | using System;
12 | using System.Collections.Generic;
13 | using System.Diagnostics;
14 | using System.Linq;
15 | using System.Threading;
16 | using System.Threading.Tasks;
17 | using Azure.Messaging.ServiceBus;
18 |
19 | sealed class ReceiverTask : PerformanceTask
20 | {
21 | readonly List receivers;
22 |
23 | public ReceiverTask(Settings settings, Metrics metrics, CancellationToken cancellationToken)
24 | : base(settings, metrics, cancellationToken)
25 | {
26 | this.receivers = new List();
27 | }
28 |
29 | protected override Task OnOpenAsync()
30 | {
31 | return Task.CompletedTask;
32 | }
33 |
34 | protected override Task OnStartAsync()
35 | {
36 | var receiverPaths = this.Settings.ReceivePaths;
37 | foreach (var receiverPath in receiverPaths)
38 | {
39 | for (int i = 0; i < this.Settings.ReceiverCount; i++)
40 | {
41 | this.receivers.Add(Task.Run(() => ReceiveTask(receiverPath)));
42 | }
43 | }
44 | return Task.WhenAll(this.receivers);
45 | }
46 |
47 | async Task ReceiveTask(string path)
48 | {
49 | var client = new ServiceBusClient(this.Settings.ConnectionString);
50 | var options = new ServiceBusReceiverOptions();
51 | options.ReceiveMode = this.Settings.ReceiveMode;
52 | options.PrefetchCount = Settings.PrefetchCount;
53 | ServiceBusReceiver receiver = client.CreateReceiver(path, options);
54 | var semaphore = new DynamicSemaphoreSlim(this.Settings.MaxInflightReceives.Value + 1);
55 | var done = new SemaphoreSlim(1); done.Wait();
56 | var sw = Stopwatch.StartNew();
57 | long totalReceives = 0;
58 | await Task.Delay(TimeSpan.FromMilliseconds(Settings.WorkDuration));
59 | this.Settings.MaxInflightReceives.Changing += (a, e) => AdjustSemaphore(e, semaphore);
60 |
61 | for (int j = 0; j < Settings.MessageCount && !this.CancellationToken.IsCancellationRequested; j ++)
62 | {
63 | var receiveMetrics = new ReceiveMetrics() { Tick = sw.ElapsedTicks };
64 |
65 | var nsec = sw.ElapsedTicks;
66 | await semaphore.WaitAsync().ConfigureAwait(false);
67 | receiveMetrics.GateLockDuration100ns = sw.ElapsedTicks - nsec;
68 |
69 | if (Settings.ReceiveBatchCount <= 1)
70 | {
71 | nsec = sw.ElapsedTicks;
72 | // we're going to unblock the receives after 10 seconds if there's no pending message
73 | receiver.ReceiveMessageAsync(TimeSpan.FromSeconds(10)).ContinueWith(async (t) =>
74 | {
75 | receiveMetrics.ReceiveDuration100ns = sw.ElapsedTicks - nsec;
76 | if (t.IsFaulted || t.IsCanceled || t.Result == null)
77 | {
78 | if ((Exception)t.Exception is ServiceBusException sbException && sbException.Reason == ServiceBusFailureReason.ServiceBusy)
79 | {
80 | receiveMetrics.BusyErrors = 1;
81 | if (!this.CancellationToken.IsCancellationRequested)
82 | {
83 | await Task.Delay(3000, this.CancellationToken).ConfigureAwait(false);
84 | }
85 | }
86 | else
87 | {
88 | receiveMetrics.Errors = 1;
89 | }
90 | Metrics.PushReceiveMetrics(receiveMetrics);
91 | semaphore.Release();
92 | if (Interlocked.Increment(ref totalReceives) >= Settings.MessageCount)
93 | {
94 | done.Release();
95 | }
96 | }
97 | else
98 | {
99 | receiveMetrics.Receives = receiveMetrics.Messages = 1;
100 | nsec = sw.ElapsedTicks;
101 |
102 | // simulate work by introducing a delay, if needed
103 | if (Settings.WorkDuration > 0)
104 | {
105 | await Task.Delay(TimeSpan.FromMilliseconds(Settings.WorkDuration));
106 | }
107 | receiver.CompleteMessageAsync(t.Result).ContinueWith(async (t1) =>
108 | {
109 | receiveMetrics.CompleteDuration100ns = sw.ElapsedTicks - nsec;
110 | if (t1.IsFaulted)
111 | {
112 | if (t1.Exception?.GetType() == typeof(ServiceBusFailureReason))
113 | {
114 | receiveMetrics.BusyErrors = 1;
115 | if (!this.CancellationToken.IsCancellationRequested)
116 | {
117 | await Task.Delay(3000, this.CancellationToken).ConfigureAwait(false);
118 | }
119 | }
120 | else
121 | {
122 | receiveMetrics.Errors = 1;
123 | }
124 | }
125 | else
126 | {
127 | receiveMetrics.Completions = receiveMetrics.CompleteCalls = 1;
128 | }
129 | Metrics.PushReceiveMetrics(receiveMetrics);
130 | semaphore.Release();
131 | if (Interlocked.Increment(ref totalReceives) >= Settings.MessageCount)
132 | {
133 | done.Release();
134 | }
135 | }).Fork();
136 | };
137 |
138 | }).Fork();
139 | }
140 | else
141 | {
142 | nsec = sw.ElapsedTicks;
143 | // we're going to unblock the receives after 10 seconds if there's no pending message
144 | receiver.ReceiveMessagesAsync(Settings.ReceiveBatchCount, TimeSpan.FromSeconds(10)).ContinueWith(async (t) =>
145 | {
146 | receiveMetrics.ReceiveDuration100ns = sw.ElapsedTicks - nsec;
147 | if (t.IsFaulted || t.IsCanceled || t.Result == null)
148 | {
149 | if ((Exception)t.Exception is ServiceBusException sbException && sbException.Reason == ServiceBusFailureReason.ServiceBusy)
150 | {
151 | receiveMetrics.BusyErrors = 1;
152 | if (!this.CancellationToken.IsCancellationRequested)
153 | {
154 | await Task.Delay(3000, this.CancellationToken).ConfigureAwait(false);
155 | }
156 | }
157 | else
158 | {
159 | receiveMetrics.Errors = 1;
160 | }
161 | Metrics.PushReceiveMetrics(receiveMetrics);
162 | semaphore.Release();
163 | if (Interlocked.Increment(ref totalReceives) >= Settings.MessageCount)
164 | {
165 | done.Release();
166 | }
167 | }
168 | else
169 | {
170 | receiveMetrics.Messages = t.Result.Count;
171 | receiveMetrics.Receives = 1;
172 | nsec = sw.ElapsedTicks;
173 | if (Settings.ReceiveMode == ServiceBusReceiveMode.PeekLock)
174 | {
175 | if (Settings.WorkDuration > 0)
176 | {
177 | // handle completes singly
178 | for (int i = 0; i < t.Result.Count; i++)
179 | {
180 | await Task.Delay(TimeSpan.FromMilliseconds(Settings.WorkDuration));
181 | await receiver.CompleteMessageAsync(t.Result[i]).ContinueWith(async (t1) =>
182 | {
183 | receiveMetrics.CompleteDuration100ns = sw.ElapsedTicks - nsec;
184 | if (t1.IsFaulted)
185 | {
186 | if ((Exception)t1.Exception is ServiceBusException sbException && sbException.Reason == ServiceBusFailureReason.ServiceBusy)
187 | {
188 | receiveMetrics.BusyErrors = 1;
189 | if (!this.CancellationToken.IsCancellationRequested)
190 | {
191 | await Task.Delay(3000, this.CancellationToken).ConfigureAwait(false);
192 | }
193 | }
194 | else
195 | {
196 | receiveMetrics.Errors = 1;
197 | }
198 | }
199 | else
200 | {
201 | receiveMetrics.CompleteCalls = 1;
202 | receiveMetrics.Completions = t.Result.Count;
203 | }
204 | Metrics.PushReceiveMetrics(receiveMetrics);
205 | semaphore.Release();
206 | if (Interlocked.Increment(ref totalReceives) >= Settings.MessageCount)
207 | {
208 | done.Release();
209 | }
210 | });
211 | }
212 | }
213 | else
214 | {
215 | // batch complete
216 | await receiver.CompleteMessageAsync((ServiceBusReceivedMessage)t.Result.Select((m) => { return m; })).ContinueWith(async (t1) =>
217 | {
218 | receiveMetrics.CompleteDuration100ns = sw.ElapsedTicks - nsec;
219 | if (t1.IsFaulted)
220 | {
221 | if ((Exception)t1.Exception is ServiceBusException sbException && sbException.Reason == ServiceBusFailureReason.ServiceBusy)
222 | {
223 | receiveMetrics.BusyErrors = 1;
224 | if (!this.CancellationToken.IsCancellationRequested)
225 | {
226 | await Task.Delay(3000, this.CancellationToken).ConfigureAwait(false);
227 | }
228 | }
229 | else
230 | {
231 | receiveMetrics.Errors = 1;
232 | }
233 | }
234 | else
235 | {
236 | receiveMetrics.CompleteCalls = 1;
237 | receiveMetrics.Completions = t.Result.Count;
238 | }
239 | Metrics.PushReceiveMetrics(receiveMetrics);
240 | semaphore.Release();
241 |
242 | // count all the messages
243 | for (int k = 0; k < t.Result.Count; k++)
244 | {
245 | if (Interlocked.Increment(ref totalReceives) >= Settings.MessageCount)
246 | {
247 | done.Release();
248 | }
249 | }
250 | });
251 | }
252 | }
253 | else
254 | {
255 | if (Settings.WorkDuration > 0)
256 | {
257 | await Task.Delay(TimeSpan.FromMilliseconds(Settings.WorkDuration));
258 | }
259 | Metrics.PushReceiveMetrics(receiveMetrics);
260 | semaphore.Release();
261 | if (Interlocked.Increment(ref totalReceives) >= Settings.MessageCount)
262 | {
263 | done.Release();
264 | }
265 | }
266 | }
267 | }).Fork();
268 |
269 | }
270 | }
271 | await done.WaitAsync();
272 | }
273 |
274 | static void AdjustSemaphore(Observable.ChangingEventArgs e, DynamicSemaphoreSlim semaphore)
275 | {
276 | if (e.NewValue > e.OldValue)
277 | {
278 | for (int i = e.OldValue; i < e.NewValue; i++)
279 | {
280 | semaphore.Grant();
281 | }
282 | }
283 | else
284 | {
285 | for (int i = e.NewValue; i < e.OldValue; i++)
286 | {
287 | semaphore.Revoke();
288 | }
289 | }
290 | }
291 | }
292 | }
293 |
--------------------------------------------------------------------------------