Quantcast
Channel: Kirk Evans Blog
Viewing all articles
Browse latest Browse all 139

Device Simulator for Event Hubs

$
0
0

This post will show how to build a simulator to send messages to Azure Event Hubs.

Background

I have been doing a lot of work with Event Hubs stream processing lately, and I find that I need different types of device simulators.  I need one that sends a few messages, one that continuously sends a few messages, one that sends a lot of messages, and so on.  I have probably 10 different console apps that do nothing more than send messages to Event Hubs in a loop, so I decided to make one simulator that fits all my needs.

This code adapts the code found in the Service Bus Event Hubs Getting Started sample.

The application makes it possible to simulate a number of devices sending X number of messages, with a sleep duration between each operation.  You can run a finite number of times, or run continuously, and the application enables the end user to cancel the operation by pressing ENTER.

image

This is a console application built with .NET, but I could have just as easily created a Java application that uses JMS to send messages.  I showed how to do this in the post Use JMS and Azure Event Hubs with Eclipse

Reusing Existing Code

I created a library called EventHubDemo.Common that has two classes, MetricEvent and EventHubManager.  Those classes are detailed in my post Scaling Azure Event Hubs Processing with Worker Roles as I use the same code between the sender (this post) and the receiver.

MetricEvent.cs
  1. using System.Runtime.Serialization;
  2.  
  3. namespace EventHubDemo.Common.Contracts
  4. {
  5.     [DataContract]
  6.     publicclassMetricEvent
  7.     {
  8.         [DataMember]
  9.         publicint DeviceId { get; set; }
  10.         [DataMember]
  11.         publicint Temperature { get; set; }
  12.     }
  13. }
EventHubManager.cs
  1. using Microsoft.ServiceBus;
  2. using Microsoft.ServiceBus.Messaging;
  3. using System;
  4. using System.Diagnostics;
  5.  
  6. namespace EventHubDemo.Common.Utility
  7. {
  8.     publicclassEventHubManager
  9.     {
  10.         publicstaticstring GetServiceBusConnectionString()
  11.         {
  12.             string connectionString = Microsoft.WindowsAzure.CloudConfigurationManager.GetSetting("Microsoft.ServiceBus.ConnectionString");
  13.             
  14.             if (string.IsNullOrEmpty(connectionString))
  15.             {
  16.                 Trace.WriteLine("Did not find Service Bus connections string in appsettings (app.config)");
  17.                 returnstring.Empty;
  18.             }
  19.             ServiceBusConnectionStringBuilder builder = newServiceBusConnectionStringBuilder(connectionString);
  20.             builder.TransportType = TransportType.Amqp;
  21.             return builder.ToString();
  22.         }
  23.  
  24.         publicstaticNamespaceManager GetNamespaceManager()
  25.         {
  26.             returnNamespaceManager.CreateFromConnectionString(GetServiceBusConnectionString());
  27.         }
  28.  
  29.         publicstaticNamespaceManager GetNamespaceManager(string connectionString)
  30.         {
  31.             returnNamespaceManager.CreateFromConnectionString(connectionString);
  32.         }
  33.  
  34.  
  35.         publicstaticvoid CreateEventHubIfNotExists(string eventHubName, int numberOfPartitions, NamespaceManager manager)
  36.         {
  37.             try
  38.             {
  39.                 // Create the Event Hub
  40.                 Trace.WriteLine("Creating Event Hub...");
  41.                 EventHubDescription ehd = newEventHubDescription(eventHubName);
  42.                 ehd.PartitionCount = numberOfPartitions;
  43.                 manager.CreateEventHubIfNotExistsAsync(ehd).Wait();
  44.             }
  45.             catch (AggregateException agexp)
  46.             {
  47.                 Trace.WriteLine(agexp.Flatten());
  48.             }
  49.         }
  50.  
  51.     }
  52. }

Creating the Console Application

I created a new console application named Sender.  I added a reference to my EventHubDemo.Common library, and added NuGet packages for Service Bus and Json.NET.

image

Realizing that others might use this program, I wanted to give a little documentation at runtime.  I created a class MyArgs.cs that is responsible for parsing arguments, providing help, and providing current values.

MyArgs.cs
  1. using System;
  2. using System.Text;
  3.  
  4. namespace EventHubDemo.Sender
  5. {
  6.     publicclassMyArgs
  7.     {
  8.         public MyArgs()
  9.         {
  10.         }
  11.  
  12.         publicstring eventHubName { get; set; }
  13.  
  14.         publicint numberOfDevices { get; set; }
  15.  
  16.         publicint numberOfMessages { get; set; }
  17.  
  18.         publicint numberOfPartitions { get; set; }        
  19.  
  20.         publicint sleepSeconds { get; set; }
  21.  
  22.         publicint iterations { get; set; }
  23.  
  24.  
  25.         publicvoid ParseArgs(string[] args)
  26.         {
  27.             if (args.Length != 6)
  28.             {
  29.                 thrownewArgumentException("Incorrect number of arguments. Expected 6 args <eventhubname> <NumberOfDevices> <NumberOfMessagesToSend> <NumberOfPartitions> <sleepSeconds> <iterations>", args.ToString());
  30.             }
  31.             else
  32.             {
  33.                 eventHubName = args[0];
  34.                 numberOfDevices = Int32.Parse(args[1]);
  35.                 numberOfMessages = Int32.Parse(args[2]);
  36.                 numberOfPartitions = Int32.Parse(args[3]);
  37.                 sleepSeconds = int.Parse(args[4]);
  38.                 iterations = int.Parse(args[5]);
  39.             }
  40.         }
  41.  
  42.         internalstring GetHelp()
  43.         {
  44.             StringBuilder sb = newStringBuilder();
  45.             sb.AppendLine("Usage: Sender.exe EventHubName NumberOfDevices NumberOfMessagesToSend NumberOfPartitions SleepSeconds Iterations");
  46.             sb.AppendLine();
  47.             sb.AppendLine("Parameters:");
  48.             sb.AppendLine("\tEventHubName:\t\tName of the Event Hub to send messages to");
  49.             sb.AppendLine("\tNumberOfDevices:\t\tNumber of devices to simulate");
  50.             sb.AppendLine("\tNumberOfMessagesToSend:\t\tNumber of messages to send");
  51.             sb.AppendLine("\tNumberOfPartitions:\t\tNumber of Event Hub partitions");
  52.             sb.AppendLine("\tSleepSeconds:\t\tNumber of seconds to sleep between iterations (0 to send as fast as possible)");
  53.             sb.AppendLine("\tIterations:\t\tNumber of iterations (-1 to continuously send)");
  54.             sb.AppendLine();
  55.  
  56.             return sb.ToString();
  57.         }
  58.  
  59.         publicoverridestring ToString()
  60.         {
  61.             StringBuilder sb = newStringBuilder();
  62.             sb.AppendLine("Event Hub name: " + eventHubName);
  63.             sb.AppendLine("Number of devices: " + numberOfDevices);
  64.             sb.AppendLine("Number of messages: " + numberOfMessages);
  65.             sb.AppendLine("Number of partitions: " + numberOfPartitions);
  66.             sb.AppendLine("Seconds to sleep between iterations: " + sleepSeconds);
  67.             sb.AppendLine("Number of iterations: " + iterations);
  68.  
  69.             return sb.ToString();
  70.         }
  71.     }
  72. }

I then adapted the code from Service Bus Event Hubs Getting Started to create a class responsible for sending messages to Event Hub.

Sender.cs
  1. using EventHubDemo.Common.Contracts;
  2. using Microsoft.ServiceBus.Messaging;
  3. using Newtonsoft.Json;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Diagnostics;
  7. using System.Text;
  8. using System.Threading.Tasks;
  9.  
  10.  
  11. namespace EventHubDemo.Sender
  12. {
  13.     publicclassSender
  14.     {        
  15.         privatestring eventHubName;
  16.         privateint numberOfDevices;
  17.         privateint numberOfMessages;
  18.  
  19.  
  20.         public Sender(string eventHubName, int numberOfDevices, int numberOfMessages)
  21.         {
  22.             this.eventHubName = eventHubName;
  23.             this.numberOfDevices = numberOfDevices;
  24.             this.numberOfMessages = numberOfMessages;            
  25.         }
  26.  
  27.         publicvoid SendEvents()
  28.         {
  29.             // Create EventHubClient
  30.             EventHubClient client = EventHubClient.Create(this.eventHubName);
  31.  
  32.             try
  33.             {
  34.                 List<Task> tasks = newList<Task>();
  35.                 
  36.                 // Send messages to Event Hub
  37.                 Trace.TraceInformation("Sending messages to Event Hub " + client.Path);
  38.  
  39.                 Random random = newRandom();
  40.                 for (int i = 0; i < this.numberOfMessages; ++i)
  41.                 {
  42.                     // Create the device/temperature metric
  43.                     MetricEvent info = newMetricEvent() { DeviceId = random.Next(numberOfDevices), Temperature = random.Next(100) };
  44.                     var serializedString = JsonConvert.SerializeObject(info);
  45.  
  46.                     EventData data = newEventData(Encoding.UTF8.GetBytes(serializedString));
  47.  
  48.                     // Set user properties if needed
  49.                     data.Properties.Add("Type", "Telemetry_" + DateTime.Now.ToLongTimeString());
  50.                     OutputMessageInfo("SENDING: ", data, info);
  51.  
  52.                     // Send the metric to Event Hub
  53.                     tasks.Add(client.SendAsync(data));
  54.                 }
  55.                 ;
  56.  
  57.                 Task.WaitAll(tasks.ToArray());
  58.             }
  59.             catch (Exception exp)
  60.             {
  61.                 Trace.TraceError("Error on send: " + exp.Message);
  62.             }
  63.  
  64.             client.CloseAsync().Wait();
  65.         }
  66.  
  67.         staticvoid OutputMessageInfo(string action, EventData data, MetricEvent info)
  68.         {
  69.             if (data == null)
  70.             {
  71.                 return;
  72.             }
  73.             if (info != null)
  74.             {
  75.                 Trace.TraceInformation("{0}: Device {1}, Temperature {2}.", action, info.DeviceId, info.Temperature);
  76.             }
  77.         }
  78.     }
  79. }

I’ve used a variation of this Sender class in a number of ways, using various loops to simulate.  What I finally settled on is to call the Sender.cs class and using a little bit of threading code.

Program.cs
  1. using EventHubDemo.Common.Utility;
  2. using System;
  3. using System.Diagnostics;
  4. using System.Threading;
  5. using System.Threading.Tasks;
  6.  
  7. namespace EventHubDemo.Sender
  8. {
  9.     classProgram
  10.     {
  11.         protectedManualResetEvent runCompleteEvent = newManualResetEvent(false);
  12.         protectedCancellationTokenSource cancellationTokenSource = newCancellationTokenSource();
  13.         privateMyArgs a;
  14.  
  15.         public Program(MyArgs args)
  16.         {
  17.             a = args;
  18.         }
  19.         staticvoid Main(string[] args)
  20.         {
  21.             MyArgs a = newMyArgs();
  22.  
  23.             try
  24.             {
  25.                 a.ParseArgs(args);
  26.             }
  27.             catch
  28.             {
  29.                 Console.WriteLine(a.GetHelp());
  30.                 return;
  31.             }
  32.             
  33.  
  34.             Trace.TraceInformation(a.ToString());
  35.  
  36.             Program p = newProgram(a);
  37.                         
  38.             var token = p.cancellationTokenSource.Token;
  39.  
  40.             Task.Factory.StartNew(() => p.Run(token, a));
  41.             Task.Factory.StartNew(() => p.WaitForEnter());
  42.  
  43.             p.runCompleteEvent.WaitOne();            
  44.             
  45.         }
  46.  
  47.         protectedvoid WaitForEnter()
  48.         {
  49.             Console.WriteLine("Press enter key to stop worker.");
  50.             Console.ReadLine();
  51.             cancellationTokenSource.Cancel();
  52.         }
  53.  
  54.  
  55.         privatevoid Run(CancellationToken token, MyArgs a)
  56.         {
  57.  
  58.             if (a.iterations == -1)
  59.             {
  60.                 //Continuously iterate
  61.                 while (!token.IsCancellationRequested)
  62.                 {
  63.                     SendMessages(a);
  64.  
  65.                     //Convert to milliseconds
  66.                     Thread.Sleep(a.sleepSeconds * 1000);
  67.                 }
  68.                 runCompleteEvent.Set();
  69.             }
  70.             else
  71.             {
  72.                 //Iterate a finite number of times, enabling the user
  73.                 //  to cancel the operation
  74.                 for (int i = 0; i < a.iterations; i++)
  75.                 {
  76.                     if (!token.IsCancellationRequested)
  77.                     {
  78.                         SendMessages(a);
  79.  
  80.                         //Convert to milliseconds
  81.                         Thread.Sleep(a.sleepSeconds * 1000);
  82.                     }
  83.                     else
  84.                     {
  85.                         break;
  86.                     }
  87.                 }
  88.                 runCompleteEvent.Set();
  89.             }                           
  90.         }
  91.  
  92.         privatestaticvoid SendMessages(MyArgs a)
  93.         {
  94.             var namespaceManager = EventHubManager.GetNamespaceManager();
  95.             EventHubManager.CreateEventHubIfNotExists(a.eventHubName, a.numberOfPartitions, namespaceManager);
  96.  
  97.             Sender s = newSender(a.eventHubName, a.numberOfDevices, a.numberOfMessages);
  98.             Trace.TraceInformation("Sending events");
  99.             s.SendEvents();
  100.         }        
  101.     }
  102. }

Our program’s entry point is, of course, the static Main method.  We parse the arguments and then reference the cancellation token.  This allows us to check for token.IsCancellationRequested to see if the user cancelled the current operation.  We then use a ManualResetEvent to wait for a signal that the application is complete.  This allows us to run for a single iteration and then terminate, or to continuously iterate. 

When the Run method is called, we check to see if the user wants to run for a finite number of times or to continuously execute.   In both cases, we enable the user to cancel the operation.

Side note: See Lync and Outlook teams?  The white screen of death is avoidable.

We call the Service Bus library to send messages to the Event Hub, and sleep for X number of seconds between iterations. 

App.Config

This part is also worth mentioning… we store the connection string for Service Bus and the Azure Storage Account connection string in app.config.  I also added the System.Diagnostics.ConsoleTraceListener so that all Trace output messages appear in the Console window.

Code Snippet
  1. <appSettings>
  2.   <!-- Service Bus specific app setings for messaging connections -->
  3.   <addkey="Microsoft.ServiceBus.ConnectionString"
  4.        value="Endpoint=sb://kirkevans.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=REDACTED=" />
  5.   <!--<add key="AzureStorageConnectionString" value="UseDevelopmentStorage=true;DevelopmentStorageProxyUri=http://127.0.0.1:10000/" />-->
  6.   <addkey="AzureStorageConnectionString"
  7.        value="DefaultEndpointsProtocol=https;AccountName=kirkestorage;AccountKey=REDACTED=="/>
  8. </appSettings>
  9. <system.diagnostics>
  10.   <traceautoflush="true">
  11.     <listeners>
  12.       <addname="consoleListener"
  13.            type="System.Diagnostics.ConsoleTraceListener"/>
  14.     </listeners>
  15.   </trace>
  16. </system.diagnostics>

Debugging With Visual Studio

To enable debugging in Visual Studio, you need to provide the command-line arguments.  Realizing that you might not have had to write a console application in a few years, just go to the project’s properties and set the command line arguments on the Debug tab.

image

The command line arguments I used were:

devicereadings 1000 5 16 1 –1

The Event Hub name is “devicereadings”, I am simulating 1000 devices, sending 5 messages per batch.  There are 16 partitions used for the Event Hub, and we sleep for 1 second between batches.  The program runs continuously until the user terminates.

The Result

We start the Sender using our desired parameters.

SNAGHTML23ee081b

To see if it is actually working, I run a worker role that processes the Event Hub streams in the local compute emulator (details on how to create this are in the post Scaling Azure Event Hubs Processing with Worker Roles).

image

Summary

This post shows how to create a simple device simulator to send messages to Azure Event Hubs.  I realize that using Console Applications is not the most interesting demo in the world, and it would be nice to do something with this data besides just dumping it to Console output.  We’ll look at that in some upcoming posts.

For More Information

Service Bus Event Hubs Getting Started

Scaling Azure Event Hubs Processing with Worker Roles

Use JMS and Azure Event Hubs with Eclipse


Viewing all articles
Browse latest Browse all 139

Trending Articles