You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

345 lines
14 KiB

  1. using MQTTnet.Client;
  2. using MQTTnet.Server;
  3. using System.Collections.Generic;
  4. using System;
  5. using System.Threading;
  6. using System.Threading.Tasks;
  7. using System.Linq;
  8. namespace MQTTnet.TestApp
  9. {
  10. /// <summary>
  11. /// Connect a number of publisher clients and one subscriber client, then publish messages and measure
  12. /// the number of messages per second that can be exchanged between publishers and subscriber.
  13. /// Measurements are performed for subscriptions containing no wildcard, a single wildcard or multiple wildcards.
  14. /// </summary>
  15. public class MessageThroughputTest
  16. {
  17. // Change these constants to suit
  18. const int NumPublishers = 5000;
  19. const int NumTopicsPerPublisher = 10;
  20. // Note: Other code changes may be required when changing this constant:
  21. const int NumSubscribers = 1; // Fixed
  22. // Number of publish calls before a response for all published messages is awaited.
  23. // This must be limited to a reasonable value that the server or TCP pipeline can handle.
  24. const int NumPublishCallsPerBatch = 250;
  25. // Message counters are set/reset in the PublishAllAsync loop
  26. int _messagesReceivedCount;
  27. int _messagesExpectedCount;
  28. CancellationTokenSource _cancellationTokenSource;
  29. MqttServer _mqttServer;
  30. Dictionary<string, Client.MqttClient> _mqttPublisherClientsByPublisherName;
  31. List<Client.MqttClient> _mqttSubscriberClients;
  32. Dictionary<string, List<string>> _topicsByPublisher;
  33. Dictionary<string, List<string>> _singleWildcardTopicsByPublisher;
  34. Dictionary<string, List<string>> _multiWildcardTopicsByPublisher;
  35. public async Task Run()
  36. {
  37. try
  38. {
  39. Console.WriteLine();
  40. Console.WriteLine("Begin message throughput test");
  41. Console.WriteLine();
  42. Console.WriteLine("Number of publishers: " + NumPublishers);
  43. Console.WriteLine("Number of published topics (total): " + NumPublishers * NumTopicsPerPublisher);
  44. Console.WriteLine("Number of subscribers: " + NumSubscribers);
  45. await Setup();
  46. await Subscribe_to_No_Wildcard_Topics();
  47. await Subscribe_to_Single_Wildcard_Topics();
  48. await Subscribe_to_Multi_Wildcard_Topics();
  49. Console.WriteLine();
  50. Console.WriteLine("End message throughput test");
  51. }
  52. catch (Exception ex)
  53. {
  54. ConsoleWriteLineError(ex.Message);
  55. }
  56. finally
  57. {
  58. await Cleanup();
  59. }
  60. }
  61. public async Task Setup()
  62. {
  63. new TopicGenerator().Generate(NumPublishers, NumTopicsPerPublisher, out _topicsByPublisher, out _singleWildcardTopicsByPublisher, out _multiWildcardTopicsByPublisher);
  64. var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
  65. var factory = new MqttFactory();
  66. _mqttServer = factory.CreateMqttServer(serverOptions);
  67. await _mqttServer.StartAsync();
  68. Console.WriteLine();
  69. Console.WriteLine("Begin connect " + NumPublishers + " publisher(s)...");
  70. var stopWatch = new System.Diagnostics.Stopwatch();
  71. stopWatch.Start();
  72. _mqttPublisherClientsByPublisherName = new Dictionary<string, Client.MqttClient>();
  73. foreach (var pt in _topicsByPublisher)
  74. {
  75. var publisherName = pt.Key;
  76. var mqttClient = factory.CreateMqttClient();
  77. var publisherOptions = new MqttClientOptionsBuilder()
  78. .WithTcpServer("localhost")
  79. .WithClientId(publisherName)
  80. .Build();
  81. await mqttClient.ConnectAsync(publisherOptions);
  82. _mqttPublisherClientsByPublisherName.Add(publisherName, mqttClient);
  83. }
  84. stopWatch.Stop();
  85. Console.Write(string.Format("{0} publisher(s) connected in {1:0.000} seconds, ", NumPublishers, stopWatch.ElapsedMilliseconds / 1000.0));
  86. Console.WriteLine(string.Format("connections per second: {0:0.000}", NumPublishers / (stopWatch.ElapsedMilliseconds / 1000.0)));
  87. _mqttSubscriberClients = new List<Client.MqttClient>();
  88. for (var i = 0; i < NumSubscribers; ++i)
  89. {
  90. var mqttClient = factory.CreateMqttClient();
  91. var subsriberOptions = new MqttClientOptionsBuilder()
  92. .WithTcpServer("localhost")
  93. .WithClientId("sub" + i)
  94. .Build();
  95. await mqttClient.ConnectAsync(subsriberOptions);
  96. mqttClient.ApplicationMessageReceivedAsync += HandleApplicationMessageReceivedAsync;
  97. _mqttSubscriberClients.Add(mqttClient);
  98. }
  99. }
  100. Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs)
  101. {
  102. // count messages and signal cancellation when expected message count is reached
  103. ++_messagesReceivedCount;
  104. if (_messagesReceivedCount == _messagesExpectedCount)
  105. {
  106. _cancellationTokenSource.Cancel();
  107. }
  108. return Implementations.PlatformAbstractionLayer.CompletedTask;
  109. }
  110. public async Task Cleanup()
  111. {
  112. foreach (var mqttClient in _mqttSubscriberClients)
  113. {
  114. await mqttClient.DisconnectAsync();
  115. mqttClient.ApplicationMessageReceivedAsync -= HandleApplicationMessageReceivedAsync;
  116. mqttClient.Dispose();
  117. }
  118. foreach (var pub in _mqttPublisherClientsByPublisherName)
  119. {
  120. var mqttClient = pub.Value;
  121. await mqttClient.DisconnectAsync();
  122. mqttClient.Dispose();
  123. }
  124. await _mqttServer.StopAsync();
  125. _mqttServer.Dispose();
  126. }
  127. /// <summary>
  128. /// Measure no-wildcard topic subscription message exchange performance
  129. /// </summary>
  130. public Task Subscribe_to_No_Wildcard_Topics()
  131. {
  132. return ProcessMessages(_topicsByPublisher, "no wildcards");
  133. }
  134. /// <summary>
  135. /// Measure single-wildcard topic subscription message exchange performance
  136. /// </summary>
  137. public Task Subscribe_to_Single_Wildcard_Topics()
  138. {
  139. return ProcessMessages(_singleWildcardTopicsByPublisher, "single wildcard");
  140. }
  141. /// <summary>
  142. /// Measure multi-wildcard topic subscription message exchange performance
  143. /// </summary>
  144. public Task Subscribe_to_Multi_Wildcard_Topics()
  145. {
  146. return ProcessMessages(_multiWildcardTopicsByPublisher, "multi wildcard");
  147. }
  148. /// <summary>
  149. /// Subcribe to all topics, then run message exchange
  150. /// </summary>
  151. public async Task ProcessMessages(Dictionary<string, List<string>> topicsByPublisher, string topicTypeDescription)
  152. {
  153. var numTopics = CountTopics(topicsByPublisher);
  154. Console.WriteLine();
  155. Console.Write(string.Format("Subscribing to {0} topics ", numTopics));
  156. ConsoleWriteInfo(string.Format("({0})", topicTypeDescription));
  157. Console.WriteLine(string.Format(" for {0} subscriber(s)...", NumSubscribers));
  158. var stopWatch = new System.Diagnostics.Stopwatch();
  159. stopWatch.Start();
  160. // each subscriber subscribes to all topics, one call per topic
  161. foreach (var subscriber in _mqttSubscriberClients)
  162. {
  163. foreach (var tp in topicsByPublisher)
  164. {
  165. var topics = tp.Value;
  166. foreach (var topic in topics)
  167. {
  168. var topicFilter = new Packets.MqttTopicFilter() { Topic = topic };
  169. await subscriber.SubscribeAsync(topicFilter).ConfigureAwait(false);
  170. }
  171. }
  172. }
  173. stopWatch.Stop();
  174. Console.Write(string.Format("{0} subscriber(s) subscribed in {1:0.000} seconds, ", NumSubscribers, stopWatch.ElapsedMilliseconds / 1000.0));
  175. Console.WriteLine(string.Format("subscribe calls per second: {0:0.000}", numTopics / (stopWatch.ElapsedMilliseconds / 1000.0)));
  176. await PublishAllAsync();
  177. Console.WriteLine(string.Format("Unsubscribing {0} topics ({1}) for {2} subscriber(s)...", numTopics, topicTypeDescription, NumSubscribers));
  178. stopWatch.Restart();
  179. // unsubscribe to all topics, one call per topic
  180. foreach (var subscriber in _mqttSubscriberClients)
  181. {
  182. foreach (var tp in topicsByPublisher)
  183. {
  184. var topics = tp.Value;
  185. foreach (var topic in topics)
  186. {
  187. var topicFilter = new Packets.MqttTopicFilter() { Topic = topic };
  188. MqttClientUnsubscribeOptions options =
  189. new MqttClientUnsubscribeOptionsBuilder()
  190. .WithTopicFilter(topicFilter)
  191. .Build();
  192. await subscriber.UnsubscribeAsync(options).ConfigureAwait(false);
  193. }
  194. }
  195. }
  196. stopWatch.Stop();
  197. Console.Write(string.Format("{0} subscriber(s) unsubscribed in {1:0.000} seconds, ", NumSubscribers, stopWatch.ElapsedMilliseconds / 1000.0));
  198. Console.WriteLine(string.Format("unsubscribe calls per second: {0:0.000}", numTopics / (stopWatch.ElapsedMilliseconds / 1000.0)));
  199. }
  200. /// <summary>
  201. /// Publish messages in batches of NumPublishCallsPerBatch, wait for messages sent to subscriber
  202. /// </summary>
  203. async Task PublishAllAsync()
  204. {
  205. Console.WriteLine("Begin message exchange...");
  206. int publisherIndexCounter = 0; // index to loop around all publishers to publish
  207. int topicIndexCounter = 0; // index to loop around all topics to publish
  208. int totalNumMessagesReceived = 0;
  209. var publisherNames = _topicsByPublisher.Keys.ToList();
  210. // There should be one message received per publish
  211. _messagesExpectedCount = NumPublishCallsPerBatch;
  212. var stopWatch = new System.Diagnostics.Stopwatch();
  213. stopWatch.Start();
  214. // Loop for a while and exchange messages
  215. while (stopWatch.ElapsedMilliseconds < 10000)
  216. {
  217. _messagesReceivedCount = 0;
  218. _cancellationTokenSource = new CancellationTokenSource();
  219. for (var publishCallCount = 0; publishCallCount < NumPublishCallsPerBatch; ++publishCallCount)
  220. {
  221. // pick a publisher
  222. var publisherName = publisherNames[publisherIndexCounter % publisherNames.Count];
  223. var publisherTopics = _topicsByPublisher[publisherName];
  224. // pick a publisher topic
  225. var topic = publisherTopics[topicIndexCounter % publisherTopics.Count];
  226. var message = new MqttApplicationMessageBuilder()
  227. .WithTopic(topic)
  228. .Build();
  229. await _mqttPublisherClientsByPublisherName[publisherName].PublishAsync(message).ConfigureAwait(false);
  230. ++topicIndexCounter;
  231. ++publisherIndexCounter;
  232. }
  233. // Wait for at least one message per publish to be received by subscriber (in the subscriber's application message handler),
  234. // then loop around to send another batch
  235. try
  236. {
  237. await Task.Delay(30000, _cancellationTokenSource.Token).ConfigureAwait(false);
  238. }
  239. catch
  240. {
  241. }
  242. _cancellationTokenSource.Dispose();
  243. if (_messagesReceivedCount < _messagesExpectedCount)
  244. {
  245. ConsoleWriteLineError(string.Format("Messages Received Count mismatch, expected {0}, received {1}", _messagesExpectedCount, _messagesReceivedCount));
  246. return;
  247. }
  248. totalNumMessagesReceived += _messagesReceivedCount;
  249. }
  250. stopWatch.Stop();
  251. System.Console.Write(string.Format("{0} messages published and received in {1:0.000} seconds, ", totalNumMessagesReceived, stopWatch.ElapsedMilliseconds / 1000.0));
  252. ConsoleWriteLineSuccess(string.Format("messages per second: {0}", (int)(totalNumMessagesReceived / (stopWatch.ElapsedMilliseconds / 1000.0))));
  253. }
  254. int CountTopics(Dictionary<string, List<string>> topicsByPublisher)
  255. {
  256. var count = 0;
  257. foreach (var tp in topicsByPublisher)
  258. {
  259. count += tp.Value.Count;
  260. }
  261. return count;
  262. }
  263. void ConsoleWriteLineError(string message)
  264. {
  265. Console.ForegroundColor = ConsoleColor.Red;
  266. Console.WriteLine(message);
  267. Console.ResetColor();
  268. }
  269. void ConsoleWriteLineSuccess(string message)
  270. {
  271. Console.ForegroundColor = ConsoleColor.Green;
  272. Console.WriteLine(message);
  273. Console.ResetColor();
  274. }
  275. void ConsoleWriteInfo(string message)
  276. {
  277. Console.ForegroundColor = ConsoleColor.White;
  278. Console.Write(message);
  279. Console.ResetColor();
  280. }
  281. }
  282. }