Load testing the Azure IoT Hub

Akshay Sura - Partner

12 Nov 2018

This past weekend I was playing with the Azure IoT hub and decided to load test it to see how much throughput can be achieved.

On this install, I ran 6 tests of 500 clients with 200 messages each and was able to get 107 Messages per second through put. I then decided to test it out by increasing the Device to Cloud partitions to see if it makes any difference. I added a new hub which has 32 partitions.

I ran a few more tests with 500 devices with 200 messages each and got about 109 Messages per second throughput. I decided to reduce the number of messages being transmitted to see if we get a better throughput and indeed we did. I tested with 500 devices with 20 messages each and I was able to achieve 294 Messages per second.

Depending on what you are trying to do, you might find a suitable scaling option. The results I got satisfied my need for the project I am working on.

  1. // Copyright (c) Microsoft. All rights reserved.
  2. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
  3.  
  4. using System ;
  5. using System . Collections . Generic ;
  6. using System . Linq ;
  7. using System . Text ;
  8. using System . Threading . Tasks ;
  9. using Microsoft . Azure . Devices . Client ;
  10. using System . Threading ;
  11. using Microsoft . Azure . Devices ;
  12.  
  13. namespace Microsoft . Azure . Devices . Client . Samples
  14. {
  15. class Program
  16. {
  17. // needed when creating devices
  18. // Example "HostName=myhub.azure-devices.net;SharedAccessKeyName=iothubowner;SharedAccessKey=0U1REMOVEDvrfUDo=";
  19. static string iotHubConnectionString  = "Your Connection String" ;
  20. static string IoTHub = iotHubConnectionString . Split ( ';' )[ 0 ]. Split ( '=' )[ 1 ];
  21. static string IoTDevicePrefix = "loadTest" ;
  22. static string commonKey  = "Your Key" ;
  23. static int deviceCount  = 500 ; // how many devices we will create and clients we will launch
  24. static int maxMessages  = 20 ; // once this count of messages are sent, the client shuts down
  25. static int messageDelaySeconds  = 0 ; // how long between each message, honoring IoT Hub Quotas
  26. static int runningDevices  = 0 ;
  27. static bool allDevicesDeleted  = false ;
  28. static int totalMessageSent  = 0 ;
  29. static int allClientStarted  = 0 ;
  30.  
  31. static void Main ( string [] args )
  32. {
  33. // if the devices already exist, you can comment out this line
  34. createDevices ( deviceCount );
  35. Console . ReadKey ();
  36. DateTime startTime  = DateTime . Now ;
  37. for ( int deviceNumber  = 1 ; deviceNumber  <= deviceCount ; deviceNumber ++)
  38. {
  39. startClient ( IoTHub , IoTDevicePrefix , deviceNumber , commonKey , maxMessages , messageDelaySeconds );
  40. }
  41.  
  42. // wait for the first few to start
  43. Thread . Sleep ( 1000 * 30 );
  44.  
  45. while ( runningDevices  != 0 )
  46. {
  47. // if we still have devices running, wait
  48. Thread . Sleep ( 3000 );
  49. }
  50. DateTime endTime  = DateTime . Now ;
  51. Console . WriteLine ( "Total Client: " + deviceCount );
  52. Console . WriteLine ( "Total Messages Sent: " + deviceCount  * maxMessages );
  53. Console . WriteLine ( "Total Execution Time: " + ( endTime  - startTime ). TotalSeconds + " seconds" );
  54. Console . WriteLine ( "Messages Per Second: " + totalMessageSent  / ( endTime  - startTime ). TotalSeconds );
  55. Thread . Sleep ( 7000 );
  56. deleteDevices ( deviceCount );
  57. Thread . Sleep ( 1000 * 30 );
  58. while ( allDevicesDeleted  != true )
  59. {
  60. // if we still have devices being deleted, wait
  61. Thread . Sleep ( 1000 );
  62. }
  63. Console . WriteLine ( "Total Client: " + deviceCount );
  64. Console . WriteLine ( "Total Messages Sent: " + deviceCount  * maxMessages );
  65. Console . WriteLine ( "Total Execution Time: " + ( endTime  - startTime ). TotalSeconds + " seconds" );
  66. Console . WriteLine ( "Messages Per Second: " + totalMessageSent  / ( endTime  - startTime ). TotalSeconds );
  67.  
  68. Console . WriteLine ( "Press any key to end" );
  69. Console . ReadKey ();
  70. }
  71.  
  72. static async  Task startClient ( string IoTHub , string IoTDevicePrefix , int deviceNumber , string commonKey , int maxMessages , int messageDelaySeconds )
  73. {
  74. allClientStarted ++;
  75. runningDevices ++;
  76. string connectionString  = "HostName=" + IoTHub + ";DeviceId=" + IoTDevicePrefix + deviceNumber  + ";SharedAccessKey=" + commonKey ;
  77. DeviceClient device  = DeviceClient . CreateFromConnectionString ( connectionString , Microsoft . Azure . Devices . Client . TransportType . Mqtt );
  78. await device . OpenAsync ();
  79. Random rnd  = new Random ();
  80. int mycounter  = 1 ;
  81. Console . WriteLine ( "Device " + IoTDevicePrefix + deviceNumber  + " started" );
  82.  
  83. while ( mycounter  <= maxMessages )
  84. {
  85. Thread . Sleep (( messageDelaySeconds  * 1000 ) + rnd . Next ( 1 , 100 ));
  86. string message  = "{ \'deviceId\': \'" + string . Concat ( IoTDevicePrefix , deviceNumber ) + "\',\'message\':\'Be My Friend!\', 'sequenceNumber': " + mycounter  + ", \'submitTime\': \'" + DateTime . UtcNow + "\'}" ;
  87. Console . WriteLine ( message );
  88. Message IoTMessage = new Microsoft . Azure . Devices . Client . Message ( Encoding . UTF8 . GetBytes ( message ));
  89. await device . SendEventAsync ( IoTMessage );
  90. totalMessageSent ++;
  91. mycounter ++;
  92. }
  93. await device . CloseAsync ();
  94. Console . WriteLine ( "Device " + IoTDevicePrefix + deviceNumber  + " ended" );
  95. runningDevices --;
  96. }
  97.  
  98. static void createDevices ( int number )
  99. {
  100. for ( int i  = 1 ; i  <= number ; i ++)
  101. {
  102. var registryManager  = RegistryManager . CreateFromConnectionString ( iotHubConnectionString );
  103. Device mydevice  = new Device ( IoTDevicePrefix + i . ToString ());
  104. mydevice . Authentication = new AuthenticationMechanism ();
  105. mydevice . Authentication . SymmetricKey . PrimaryKey = commonKey ;
  106. mydevice . Authentication . SymmetricKey . SecondaryKey = commonKey ;
  107. try
  108. {
  109. registryManager . AddDeviceAsync ( mydevice ). Wait ();
  110. Console . WriteLine ( "Adding device: " + IoTDevicePrefix + i . ToString ());
  111. }
  112. catch ( Exception er )
  113. {
  114. Console . WriteLine ( " Error adding device: " + IoTDevicePrefix + i . ToString () + " error: " + er . InnerException . Message );
  115. }
  116. }
  117.  
  118. }
  119. static async  void deleteDevices ( int number )
  120. {
  121. for ( int i  = 1 ; i  <= number ; i ++)
  122. {
  123. var registryManager  = RegistryManager . CreateFromConnectionString ( iotHubConnectionString );
  124.  
  125. try
  126. {
  127. Device mydevice  = await registryManager . GetDeviceAsync ( IoTDevicePrefix + i . ToString ());
  128. registryManager . RemoveDeviceAsync ( mydevice ). Wait ();
  129. Console . WriteLine ( "Deleting device " + IoTDevicePrefix + i . ToString ());
  130. }
  131. catch ( Exception er ) { }
  132.  
  133. }
  134. allDevicesDeleted  = true ;
  135.  
  136. }
  137. }
  138. }
  139. // Copyright (c) Microsoft. All rights reserved.
  140. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
  141.  
  142. // This application uses the Microsoft Azure Event Hubs Client for .NET
  143. // For samples see: https://github.com/Azure/azure-event-hubs/tree/master/samples/DotNet
  144. // For documenation see: https://docs.microsoft.com/azure/event-hubs/
  145. using System ;
  146. using Microsoft . Azure . EventHubs ;
  147. using System . Threading . Tasks ;
  148. using System . Threading ;
  149. using System . Text ;
  150. using System . Collections . Generic ;
  151. using Newtonsoft . Json ;
  152. using System . Linq ;
  153.  
  154. namespace read_d2c_messages
  155. {
  156. class ReadDeviceToCloudMessages
  157. {
  158. private static int noOfMessages  = 0 ;

  159. // az iot hub show --query properties.eventHubEndpoints.events.endpoint --name {your IoT Hub name}
  160. private readonly static string s_eventHubsCompatibleEndpoint  = "Your End Point" ;
  161.  
  162. // Event Hub-compatible name
  163. // az iot hub show --query properties.eventHubEndpoints.events.path --name {your IoT Hub name}
  164. private readonly static string s_eventHubsCompatiblePath  = "bemyfriendiothub" ;
  165.  
  166. // az iot hub policy show --name iothubowner --query primaryKey --hub-name {your IoT Hub name}
  167. private readonly static string s_iotHubSasKey  = "Your Key" ;
  168. private readonly static string s_iotHubSasKeyName  = "iothubowner" ;
  169. private static EventHubClient s_eventHubClient ;
  170.  
  171. // Asynchronously create a PartitionReceiver for a partition and then start
  172. // reading any messages sent from the simulated client.
  173. private static async  Task ReceiveMessagesFromDeviceAsync ( string partition , CancellationToken ct )
  174. {
  175. //messages.Clear();
  176. // Create the receiver using the default consumer group.
  177. // For the purposes of this sample, read only messages sent since
  178. // the time the receiver is created. Typically, you don't want to skip any messages.
  179. var eventHubReceiver  = s_eventHubClient . CreateReceiver ( "$Default" , partition , EventPosition . FromEnqueuedTime ( DateTime . Now ));
  180. Console . WriteLine ( "Create receiver on partition: " + partition );
  181. while ( true )
  182. {
  183. if ( ct . IsCancellationRequested ) break ;
  184. //Console.WriteLine("Listening for messages on: " + partition);
  185. // Check for EventData - this methods times out if there is nothing to retrieve.
  186. var events  = await eventHubReceiver . ReceiveAsync ( 100 );
  187.  
  188. // If there is data in the batch, process it.
  189. if ( events  == null ) continue ;
  190.  
  191. foreach ( EventData eventData  in events )
  192. {
  193. string message  = Encoding . UTF8 . GetString ( eventData . Body . Array );
  194. noOfMessages ++;
  195. Console . WriteLine ( "Message: {0} Messages Received:{1}" , message , noOfMessages . ToString ());
  196. }
  197. }
  198. }
  199.  
  200. private static async  Task Main ( string [] args )
  201. {
  202. noOfMessages  = 0 ;
  203. Console . WriteLine ( "IoT Hub Quickstarts - Read device to cloud messages. Ctrl-C to exit.n" );
  204.  
  205. // Create an EventHubClient instance to connect to the
  206. // IoT Hub Event Hubs-compatible endpoint.
  207. var connectionString  = new EventHubsConnectionStringBuilder ( new Uri ( s_eventHubsCompatibleEndpoint ), s_eventHubsCompatiblePath , s_iotHubSasKeyName , s_iotHubSasKey );
  208. s_eventHubClient  = EventHubClient . CreateFromConnectionString ( connectionString . ToString ());
  209.  
  210. // Create a PartitionReciever for each partition on the hub.
  211. var runtimeInfo  = await s_eventHubClient . GetRuntimeInformationAsync ();
  212. var d2cPartitions  = runtimeInfo . PartitionIds ;
  213.  
  214. CancellationTokenSource cts  = new CancellationTokenSource ();
  215.  
  216. Console . CancelKeyPress += ( s , e ) =>
  217. {
  218. Console . WriteLine ( "Number of Messages Received: {0}" , noOfMessages . ToString ());
  219. };
  220.  
  221. var tasks  = new List < Task >();
  222. foreach ( string partition  in d2cPartitions )
  223. {
  224. tasks . Add ( ReceiveMessagesFromDeviceAsync ( partition , cts . Token ));
  225. }
  226.  
  227. // Wait for all the PartitionReceivers to finsih.
  228. Task . WaitAll ( tasks . ToArray ());
  229. }
  230. }
  231. }

Useful Links:

  • Load Testing Azure IoT Hub with simulated client
  • Choose the right IoT Hub tier for your solution
  • Tutorial: Use a simulated device to test connectivity with your IoT hub
  • Azure IoT Samples for Csharp (.NET)
  • Tutorial: Develop a C# IoT Edge module and deploy to your simulated device

If you have any questions or concerns, please get in touch with me. (@akshaysura13 on Twitter or on Slack).


Sign up to our newsletter

Share on social media

Akshay Sura

Akshay is a nine-time Sitecore MVP and a two-time Kontent.ai. In addition to his work as a solution architect, Akshay is also one of the founders of SUGCON North America 2015, SUGCON India 2018 & 2019, Unofficial Sitecore Training, and Sitecore Slack.

Akshay founded and continues to run the Sitecore Hackathon. As one of the founding partners of Konabos Consulting, Akshay will continue to work with clients to lead projects and mentor their existing teams.


Subscribe to newsletter