From b647975625a4bf20529fd002ee054c8e9f451054 Mon Sep 17 00:00:00 2001 From: Peter Giacomo Lombardo Date: Thu, 14 Mar 2024 11:51:00 +0100 Subject: [PATCH] Performance Benchmarks (#130) --- .editorconfig | 1 - .vscode/launch.json | 1 + .vscode/tasks.json | 86 +++++++++++-------- .../ClientBenchmarkApp/ClientBenchmark.cs | 71 +++++++++++++++ .../ClientBenchmarkApp.csproj | 15 ++++ Benchmarks/ClientBenchmarkApp/Program.cs | 11 +++ Benchmarks/ClientBenchmarkApp/README.md | 5 ++ .../Client/HiveMQClientTrafficProcessor.cs | 7 +- Source/HiveMQtt/Client/HiveMQClientUtil.cs | 2 +- .../HiveMQtt/Client/PublishMessageBuilder.cs | 5 +- .../Client/Results/UnsubscribeResult.cs | 5 +- .../Client/SubscribeOptionsBuilder.cs | 5 +- .../Client/UnsubscribeOptionsBuilder.cs | 5 +- 13 files changed, 160 insertions(+), 59 deletions(-) create mode 100644 Benchmarks/ClientBenchmarkApp/ClientBenchmark.cs create mode 100644 Benchmarks/ClientBenchmarkApp/ClientBenchmarkApp.csproj create mode 100644 Benchmarks/ClientBenchmarkApp/Program.cs create mode 100644 Benchmarks/ClientBenchmarkApp/README.md diff --git a/.editorconfig b/.editorconfig index ef1bbd10..cade6374 100644 --- a/.editorconfig +++ b/.editorconfig @@ -188,7 +188,6 @@ csharp_style_unused_value_assignment_preference = discard_variable:suggestion dotnet_diagnostic.IDE0059.severity = suggestion dotnet_diagnostic.IDE0002.severity = none dotnet_diagnostic.IDE0010.severity = none -dotnet_diagnostic.IDE0021.severity = none dotnet_diagnostic.IDE0028.severity = none dotnet_diagnostic.IDE0049.severity = none dotnet_diagnostic.IDE0053.severity = none diff --git a/.vscode/launch.json b/.vscode/launch.json index 27ca2a33..153513e2 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,6 +1,7 @@ { "version": "0.2.0", "configurations": [ + { // Use IntelliSense to find out which attributes exist for C# debugging // Use hover for the description of the existing attributes diff --git a/.vscode/tasks.json b/.vscode/tasks.json index a535c974..81c1eb4d 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,41 +1,55 @@ { "version": "2.0.0", "tasks": [ - { - "label": "build", - "command": "dotnet", - "type": "process", - "args": [ - "build", - "${workspaceFolder}/Tests/HiveMQtt.Test/HiveMQtt.Test.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "publish", - "command": "dotnet", - "type": "process", - "args": [ - "publish", - "${workspaceFolder}/Tests/HiveMQtt.Test/HiveMQtt.Test.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "watch", - "command": "dotnet", - "type": "process", - "args": [ - "watch", - "run", - "--project", - "${workspaceFolder}/Tests/HiveMQtt.Test/HiveMQtt.Test.csproj" - ], - "problemMatcher": "$msCompile" + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/Tests/HiveMQtt.Test/HiveMQtt.Test.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/Tests/HiveMQtt.Test/HiveMQtt.Test.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/Tests/HiveMQtt.Test/HiveMQtt.Test.csproj" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "build ClientBenchmarkApp", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/Benchmarks/ClientBenchmarkApp/ClientBenchmarkApp.csproj" + ], + "problemMatcher": "$msCompile", + "group": { + "kind": "build", + "isDefault": true } + } ] -} \ No newline at end of file +} diff --git a/Benchmarks/ClientBenchmarkApp/ClientBenchmark.cs b/Benchmarks/ClientBenchmarkApp/ClientBenchmark.cs new file mode 100644 index 00000000..a55af0f6 --- /dev/null +++ b/Benchmarks/ClientBenchmarkApp/ClientBenchmark.cs @@ -0,0 +1,71 @@ +namespace ClientBenchmarkApp; + +using System; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Running; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Engines; + +using HiveMQtt.Client; +using HiveMQtt.Client.Options; +using HiveMQtt.MQTT5; +using HiveMQtt.MQTT5.ReasonCodes; +using HiveMQtt.MQTT5.Types; + +[SimpleJob(RunStrategy.Monitoring, iterationCount: 10, id: "MonitoringJob")] +public class ClientBenchmarks : IDisposable +{ + private readonly string smallPayload = new string(/*lang=json,strict*/ "{\"interference\": \"1029384\"}"); + + private HiveMQClient client; + + [GlobalSetup] + public async Task SetupAsync() + { + var options = new HiveMQClientOptions + { + Host = "127.0.0.1", + Port = 1883, + }; + + this.client = new HiveMQClient(options); + Console.WriteLine($"Connecting to {options.Host} on port {options.Port}..."); + await this.client.ConnectAsync().ConfigureAwait(false); + + if (this.client.IsConnected()) + { + Console.WriteLine("HiveMQ client connected."); + } + else + { + Console.WriteLine("Client failed to connect!"); + } + } + + [GlobalCleanup] + public async Task CleanUpAsync() + { + Console.WriteLine("Disconnecting from HiveMQ..."); + await this.client.DisconnectAsync().ConfigureAwait(false); + } + + [Benchmark(Description = "Publish a QoS 0 messages to the broker.")] + public async Task PublishQoS0MessageAsync() + { + await this.client.PublishAsync("benchmarks/PublishQoS0Messages", this.smallPayload).ConfigureAwait(false); + } + + [Benchmark(Description = "Publish a QoS 1 messages to the broker.")] + public async Task PublishQoS1MessageAsync() + { + await this.client.PublishAsync("benchmarks/PublishQoS1Messages", this.smallPayload, QualityOfService.AtLeastOnceDelivery).ConfigureAwait(false); + } + + [Benchmark(Description = "Publish a QoS 2 messages to the broker.")] + public async Task PublishQoS2MessageAsync() + { + await this.client.PublishAsync("benchmarks/PublishQoS1Messages", this.smallPayload, QualityOfService.ExactlyOnceDelivery).ConfigureAwait(false); + } + + public void Dispose() => GC.SuppressFinalize(this); +} diff --git a/Benchmarks/ClientBenchmarkApp/ClientBenchmarkApp.csproj b/Benchmarks/ClientBenchmarkApp/ClientBenchmarkApp.csproj new file mode 100644 index 00000000..8e50f7b4 --- /dev/null +++ b/Benchmarks/ClientBenchmarkApp/ClientBenchmarkApp.csproj @@ -0,0 +1,15 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + diff --git a/Benchmarks/ClientBenchmarkApp/Program.cs b/Benchmarks/ClientBenchmarkApp/Program.cs new file mode 100644 index 00000000..24f91f89 --- /dev/null +++ b/Benchmarks/ClientBenchmarkApp/Program.cs @@ -0,0 +1,11 @@ +namespace ClientBenchmarkApp; + +using System; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Running; + +public class Program +{ + public static void Main(string[] args) + => BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); +} diff --git a/Benchmarks/ClientBenchmarkApp/README.md b/Benchmarks/ClientBenchmarkApp/README.md new file mode 100644 index 00000000..99b5e89b --- /dev/null +++ b/Benchmarks/ClientBenchmarkApp/README.md @@ -0,0 +1,5 @@ +# Benchmarks + +The benchmarks are built with [BenchmarkDotNet](https://benchmarkdotnet.org) and can be run with: + +`dotnet run ClientBenchmarkApp.csproj -c Release` diff --git a/Source/HiveMQtt/Client/HiveMQClientTrafficProcessor.cs b/Source/HiveMQtt/Client/HiveMQClientTrafficProcessor.cs index b59a3f12..233ec5dd 100644 --- a/Source/HiveMQtt/Client/HiveMQClientTrafficProcessor.cs +++ b/Source/HiveMQtt/Client/HiveMQClientTrafficProcessor.cs @@ -46,7 +46,6 @@ private Task TrafficOutflowProcessorAsync(CancellationToken cancellationTo { var stopWatch = new Stopwatch(); var keepAlivePeriod = this.Options.KeepAlive / 2; - TimeSpan elapsed; stopWatch.Start(); @@ -54,9 +53,7 @@ private Task TrafficOutflowProcessorAsync(CancellationToken cancellationTo while (this.connectState != ConnectState.Disconnected) { - elapsed = stopWatch.Elapsed; - - if (elapsed > TimeSpan.FromSeconds(keepAlivePeriod)) + if (stopWatch.Elapsed > TimeSpan.FromSeconds(keepAlivePeriod)) { // Send PingReq Logger.Trace("--> PingReq"); @@ -80,7 +77,7 @@ private Task TrafficOutflowProcessorAsync(CancellationToken cancellationTo Logger.Trace($"TrafficOutflowProcessor: {this.sendQueue.Count} packets waiting to be sent."); - // Batch load up to 20 queued packets + // Batch load up to 50 queued packets List packetsToSend = new(); while (this.sendQueue.TryDequeue(out var p)) { diff --git a/Source/HiveMQtt/Client/HiveMQClientUtil.cs b/Source/HiveMQtt/Client/HiveMQClientUtil.cs index 18993be5..c69646c6 100644 --- a/Source/HiveMQtt/Client/HiveMQClientUtil.cs +++ b/Source/HiveMQtt/Client/HiveMQClientUtil.cs @@ -99,7 +99,7 @@ public static bool MatchTopic(string pattern, string candidate) if (pattern == "+") { - // A subscription to “+” will not receive any messages published to a topic containing a $ + // A subscription to “+” will not receive any messages published to a topic beginning with a $ or / if (candidate.StartsWith("$", System.StringComparison.CurrentCulture) || candidate.StartsWith("/", System.StringComparison.CurrentCulture)) { diff --git a/Source/HiveMQtt/Client/PublishMessageBuilder.cs b/Source/HiveMQtt/Client/PublishMessageBuilder.cs index 58f7214c..979bcd85 100644 --- a/Source/HiveMQtt/Client/PublishMessageBuilder.cs +++ b/Source/HiveMQtt/Client/PublishMessageBuilder.cs @@ -25,10 +25,7 @@ public class PublishMessageBuilder /// private readonly MQTT5PublishMessage message; - public PublishMessageBuilder() - { - this.message = new MQTT5PublishMessage(); - } + public PublishMessageBuilder() => this.message = new MQTT5PublishMessage(); /// /// Sets the payload of the publish message. diff --git a/Source/HiveMQtt/Client/Results/UnsubscribeResult.cs b/Source/HiveMQtt/Client/Results/UnsubscribeResult.cs index 57a23a01..fa6eddd3 100644 --- a/Source/HiveMQtt/Client/Results/UnsubscribeResult.cs +++ b/Source/HiveMQtt/Client/Results/UnsubscribeResult.cs @@ -19,10 +19,7 @@ namespace HiveMQtt.Client.Results; public class UnsubscribeResult { - public UnsubscribeResult() - { - this.Subscriptions = new List(); - } + public UnsubscribeResult() => this.Subscriptions = new List(); public List Subscriptions { get; set; } } diff --git a/Source/HiveMQtt/Client/SubscribeOptionsBuilder.cs b/Source/HiveMQtt/Client/SubscribeOptionsBuilder.cs index b921cfcd..ec3bb65f 100644 --- a/Source/HiveMQtt/Client/SubscribeOptionsBuilder.cs +++ b/Source/HiveMQtt/Client/SubscribeOptionsBuilder.cs @@ -23,10 +23,7 @@ public class SubscribeOptionsBuilder { private readonly SubscribeOptions options; - public SubscribeOptionsBuilder() - { - this.options = new SubscribeOptions(); - } + public SubscribeOptionsBuilder() => this.options = new SubscribeOptions(); /// /// Adds a subscription to the list of subscriptions to be sent in the subscribe call. diff --git a/Source/HiveMQtt/Client/UnsubscribeOptionsBuilder.cs b/Source/HiveMQtt/Client/UnsubscribeOptionsBuilder.cs index 4a97f885..ee7db7cc 100644 --- a/Source/HiveMQtt/Client/UnsubscribeOptionsBuilder.cs +++ b/Source/HiveMQtt/Client/UnsubscribeOptionsBuilder.cs @@ -22,10 +22,7 @@ public class UnsubscribeOptionsBuilder { private readonly UnsubscribeOptions options; - public UnsubscribeOptionsBuilder() - { - this.options = new UnsubscribeOptions(); - } + public UnsubscribeOptionsBuilder() => this.options = new UnsubscribeOptions(); /// /// Adds a single subscription to the UnsubscribeOption.