<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:atom="https://www.w3.org/2005/Atom" xmlns:sy="https://purl.org/rss/1.0/modules/syndication/" xmlns:dc="https://purl.org/dc/elements/1.1/" xmlns:content="https://purl.org/rss/1.0/modules/content/" xmlns:wfw="https://wellformedweb.org/CommentAPI/" xmlns:slash="https://purl.org/rss/1.0/modules/slash/"
  version="2.0">
  <channel>
    <title>mgrichard</title>
    <atom:link
      href="https://docs.microsoft.com/archive/blogs/mgrichard/feed.xml"
      rel="self"
      type="application/rss+xml" />
    <link>https://docs.microsoft.com/archive/blogs/mgrichard/feed.xml</link>
    <description />
    <lastBuildDate>Wed, 17 Apr 2019 04:10:54 GMT</lastBuildDate>
    <language>en-US</language>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>1</sy:updateFrequency>
    <item>
      <title>Using Reactive Extensions (Rx) and Azure Service Bus for Centralizing Session Logging to Azure Table Storage at Scale</title>
      <link>https://docs.microsoft.com/archive/blogs/mgrichard/using-reactive-extensions-rx-and-azure-service-bus-for-centralizing-session-logging-to-azure-table-storage-at-scale</link>
      <pubDate>Thu, 08 May 2014 15:34:00 GMT</pubDate>
      <dc:creator><![CDATA[Martin Richard]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/mgrichard/2014/05/08/using-reactive-extensions-rx-and-azure-service-bus-for-centralizing-session-logging-to-azure-table-storage-at-scale/</guid>
      <description><![CDATA[Session data can be used by both DevOps for diagnostics and monitoring and by the business to...]]></description>
      <content:encoded><![CDATA[<p>Session data can be used by both DevOps for diagnostics and monitoring and by the business to identify and respond to trends in customer behavior.&nbsp;&nbsp; Proper collation of session data from disparate sources is thus crucial to a successful service.&nbsp; Here I start my adventure in learning Reactive Extensions (Rx) by applying Rx with Azure Service Bus to batch together logging operations for persistence into Azure Table Storage.</p>
<p>There is an abundance of documentation on how to use service bus for scale and redundancy so my example will be slightly simplified and I focus mainly on the consumption of Service Bus Brokered Messages using Rx.&nbsp; I use subscriptions and topics in this example but the code could be adapted for another queuing system.&nbsp; My example will simulate the following architecture:</p>
<p>&nbsp;<a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/2450.blog%20post%20rx%20sb%20arch.PNG" original-url="https://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/2450.blog-post-rx-sb-arch.PNG"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/2450.blog%20post%20rx%20sb%20arch.PNG" original-url="https://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/2450.blog-post-rx-sb-arch.PNG" alt="" border="0" /></a></p>
<p>&nbsp;</p>
<p>Here we see servers in multiple data centers are partitioning their log messages across several Service Bus Topics.&nbsp; A deployment of worker roles reads the messages from the associated subscriptions, aggregates them into batches partitioned by a SessionId using Rx and logs them into Azure Table Storage.&nbsp; A good practice would be to provide a fail over service bus in another data center but our example will omit that.</p>
<p>First let&rsquo;s define an interface for log messages:</p>
<p>&nbsp;</p>
<pre class="scroll"><code class="csharp"> public interface ILogMessage<br /> {<br /> string SessionId { get; set; }<br /> }</code></pre>
<p>&nbsp;</p>
<p>We will use this interface to define a helper class to read messages from the service bus.&nbsp; First let&rsquo;s design a wrapper class to wrap our log message and the underlying service bus BrokeredMessage object:</p>
<pre class="scroll"><code class="csharp"> public class WrappedLoggedMessage&lt;T&gt; where T:ILogMessage<br /> {<br /> public WrappedLoggedMessage(BrokeredMessage brokeredMessage)<br /> {<br /> _brokeredMessage = brokeredMessage;<br /> _t = Newtonsoft.Json.JsonConvert.DeserializeObject&lt;T&gt;(brokeredMessage.GetBody&lt;string&gt;());<br /> }<br /> <br /> <br /> private readonly BrokeredMessage _brokeredMessage;<br /> private readonly T _t;<br /> public T Message { get { return _t; } }<br /> public string SessionId { get { return _t.SessionId; } }<br /> <br /> public async Task Abandon()<br /> {<br /> <br /> await _brokeredMessage.AbandonAsync();<br /> }<br /> <br /> public async Task Complete()<br /> {<br /> await _brokeredMessage.CompleteAsync();<br /> }<br /> <br /> }</code></pre>
<p>&nbsp;</p>
<p>With our wrapped log object we look at a simple way to turn a stream of brokered messages from service bus subscription clients into an <em>IObservable</em> stream:</p>
<pre class="scroll"><code class="csharp"> public class SubscriptionClientsToObservableHelper&lt;T&gt; where T:ILogMessage <br /> {<br /> <br /> <br /> private readonly int _batchSize;<br /> private const int MinBackOff = 10;<br /> private const int MaxBackOff = 60000;<br /> <br /> private SubscriptionClient[] _subscriptionClients;<br /> <br /> public SubscriptionClientsToObservableHelper(IEnumerable&lt;SubscriptionClient&gt; subscriptionClients, int batchSize)<br /> {<br /> <br /> _subscriptionClients = subscriptionClients.ToArray();<br /> _batchSize = batchSize;<br /> }<br /> <br /> <br /> public IObservable&lt;WrappedLoggedMessage&lt;T&gt;&gt; PollMessages()<br /> {<br /> return Observable.Create&lt;WrappedLoggedMessage&lt;T&gt;&gt;(observer =&gt;<br /> {<br /> var isCancelled = false;<br /> ((Action)async delegate<br /> {<br /> try<br /> {<br /> var query = from client in _subscriptionClients<br /> select Task.Run(async () =&gt;<br /> {<br /> var delay = MinBackOff;<br /> while (true)<br /> {<br /> IEnumerable&lt;BrokeredMessage&gt; result = null;<br /> try<br /> {<br /> result = await client.ReceiveBatchAsync(_batchSize);<br /> }<br /> catch (Exception e)<br /> {<br /> Trace.TraceError(e.ToString());<br /> }<br /> int messageCount = 0;<br /> if (result != null)<br /> {<br /> <br /> foreach (var msg in result)<br /> {<br /> try<br /> {<br /> messageCount++;<br /> var wrapper = new WrappedLoggedMessage&lt;T&gt;(msg);<br /> <br /> lock (observer)<br /> {<br /> observer.OnNext(wrapper);<br /> }<br /> }<br /> catch (Exception e)<br /> {<br /> Trace.TraceError(e.ToString());<br /> }<br /> }<br /> <br /> }<br /> <br /> if (messageCount &gt; 0)<br /> {<br /> delay = MinBackOff;<br /> }<br /> <br /> if (messageCount == 0)<br /> {<br /> await Task.Delay(delay);<br /> delay *= 2;<br /> delay = Math.Min(delay, MaxBackOff);<br /> }<br /> <br /> if (isCancelled)<br /> {<br /> return;<br /> }<br /> <br /> }<br /> <br /> });<br /> <br /> await Task.WhenAll(query);<br /> <br /> }<br /> catch (Exception e)<br /> {<br /> observer.OnError(e);<br /> Trace.TraceError(e.ToString());<br /> <br /> }<br /> finally<br /> {<br /> observer.OnCompleted();<br /> }<br /> })();<br /> <br /> return () =&gt; isCancelled = true;<br /> });<br /> }<br /> <br /> <br /> <br /> <br /> }</code></pre>
<p>&nbsp;</p>
<p>This was a fairly straightforward exercise although I did go down a wrong turn when I read all messages from the clients and then processed them.&nbsp; This is because <em>ReceiveBatchAsync()</em> was not returning immediately if no messages were received.&nbsp; Switching to a simple Task per subscription client and then synchronizing the <em>OnNext()</em> call was much more efficient.</p>
<p>We now focus on consuming our messaging stream.&nbsp; First we define an abstract class:</p>
<pre class="scroll"><code class="csharp"> public abstract class LogMessageObserver&lt;T&gt; where T:ILogMessage<br /> {<br /> private IObservable&lt;WrappedLoggedMessage&lt;T&gt;&gt; _observable;<br /> private readonly int _observableGroupBufferTimeMilliSeconds;<br /> private readonly int _observableGroupBufferSize;<br /> private readonly object locker = new object();<br /> public LogMessageObserver(int observableGroupBufferTimeMilliSeconds,<br /> int observableGroupBufferSize)<br /> {<br /> _observableGroupBufferTimeMilliSeconds = observableGroupBufferTimeMilliSeconds;<br /> _observableGroupBufferSize = observableGroupBufferSize;<br /> }<br /> <br /> <br /> public long Logged { get; private set; }<br /> <br /> public void Start(SubscriptionClientsToObservableHelper&lt;T&gt; helper)<br /> {<br /> _observable = helper.PollMessages();<br /> <br /> var groupedLogs = _observable.GroupByUntil(c =&gt; c.SessionId,<br /> g =&gt; Observable.Timer(TimeSpan.FromSeconds(60)).Take(1));<br /> <br /> groupedLogs.Subscribe((groupedObs) =&gt;<br /> {<br /> <br /> var buffered = groupedObs.Buffer(TimeSpan.FromMilliseconds(_observableGroupBufferTimeMilliSeconds), _observableGroupBufferSize);<br /> <br /> var query = buffered.Select(logs =&gt; Observable.FromAsync(async () =&gt;<br /> {<br /> bool exeptionOccurred = false;<br /> int loggedCount = 0;<br /> <br /> if (logs == null || logs.Count == 0)<br /> return;<br /> <br /> try<br /> {<br /> loggedCount = await Log(from l in logs select l.Message);<br /> }<br /> catch (Exception logEx)<br /> {<br /> exeptionOccurred = true;<br /> // could end up with double messages<br /> Trace.TraceError(logEx.ToString());<br /> }<br /> <br /> try<br /> {<br /> // currently use all or nothing approach to marking messages as abandon/completed<br /> if (loggedCount != logs.Count)<br /> {<br /> // attempt to release messages<br /> List&lt;Task&gt; abandonTasks = new List&lt;Task&gt;();<br /> foreach (var m in logs)<br /> {<br /> var t = m.Abandon();<br /> abandonTasks.Add(t);<br /> }<br /> await Task.WhenAll(abandonTasks);<br /> }<br /> else<br /> {<br /> List&lt;Task&gt; completeTasks = new List&lt;Task&gt;();<br /> <br /> foreach (var m in logs)<br /> {<br /> var t = m.Complete();<br /> completeTasks.Add(t);<br /> }<br /> <br /> await Task.WhenAll(completeTasks);<br /> <br /> var complete =<br /> (from t in completeTasks where t.IsCompleted &amp;&amp; !t.IsFaulted select t).ToList();<br /> lock (locker)<br /> {<br /> int count = complete.Count();<br /> Logged += count;<br /> }<br /> }<br /> }<br /> catch (Exception ex)<br /> {<br /> exeptionOccurred = true;<br /> if (ex is TimeoutException)<br /> {<br /> <br /> }<br /> else<br /> {<br /> Trace.TraceError(ex.ToString());<br /> }<br /> <br /> }<br /> <br /> if (exeptionOccurred)<br /> {<br /> // throttle if exception...<br /> await Task.Delay(2000);<br /> }<br /> <br /> return;<br /> })).Concat();<br /> <br /> query.Subscribe();<br /> });<br /> <br /> return;<br /> }<br /> /// &lt;summary&gt;<br /> /// Log method to permanent storage. <br /> /// &lt;/summary&gt;<br /> /// &lt;param name="messages"&gt;wrapped messages batch from same partition (sessionId) &lt;/param&gt;<br /> /// &lt;returns&gt; 0 or number messages logged&lt;/returns&gt;<br /> public abstract Task&lt;int&gt; Log(IEnumerable&lt;T&gt; messages);<br /> }</code></pre>
<p>&nbsp;</p>
<p>There are a few points of interest in this class&rsquo;s <em>Start</em> method.&nbsp; First, I used the Rx <em>GroupByUntil()</em> extension to partition the stream by the <em>ILogger</em>.<em>SessionID</em> property.&nbsp; Notice the use of the <em>Timer()</em> and <em>Take()</em> call to provide a group expiration mechanism to prevent memory leaks.&nbsp; Second, we use the Rx <em>Buffer()</em> method to group messages from the Session together. &nbsp;This allows me to batch together messages for storage to the same partition or file.&nbsp; Third, I used <em>Observable.FromAsync()</em> in conjunction with the <em>Concat()</em> method to serially log the messages.&nbsp; In my slightly simplified example we use an all or nothing approach to decide whether to abandon or complete the underlying brokered messages.&nbsp; This approach introduces the possibility of duplicates if some of the logs were inserted or if a <em>Complete()</em> call fails.&nbsp; Your own implementations may stress throughput over reliability and thus use a different approach.&nbsp;</p>
<p>For our example we&rsquo;ll use a simple log message object that we will persist in Azure Table storage:</p>
<pre class="scroll"><code class="csharp"> public class LogMessage :ILogMessage<br /> {<br /> public string SessionId { get; set; }<br /> public string LogMessageId { get; set; }<br /> public DateTime TimeStamp { get; set; }<br /> public string LogData { get; set; }<br /> }</code></pre>
<pre class="scroll"><code class="csharp"> public class TableLogMessage : TableEntity<br /> {<br /> public TableLogMessage()<br /> {<br /> <br /> }<br /> public TableLogMessage(LogMessage log)<br /> {<br /> this.PartitionKey = log.SessionId;<br /> this.RowKey = Guid.NewGuid().ToString();<br /> this.LogData = log.LogData;<br /> this.LogMessageId = log.LogMessageId;<br /> this.LogTimeStamp = log.TimeStamp;<br /> }<br /> <br /> // this is just to confirm the partitioning feel free to omit<br /> public string RoleId { get; set; }<br /> <br /> <br /> public string LogSessionId { get { return this.PartitionKey; } }<br /> public string LogMessageId { get; set; }<br /> public DateTime LogTimeStamp { get; set; }<br /> public string LogData { get; set; }<br /> <br /> <br /> <br /> }</code></pre>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>We will implement the Log method as follows:</p>
<pre class="scroll"><code class="csharp"> public class ConcreteMessageObserver : LogMessageObserver&lt;LogMessage&gt;<br /> {<br /> private const int MaxTableInsertBatchSize = 100;<br /> private string _connectionString;<br /> private string _logTableName;<br /> private string _roleId;<br /> public ConcreteMessageObserver( int observableGroupBufferTimeMilliSeconds,<br /> int observableGroupBufferSize,string connectionString, string logTableName = "logmessagetable") : base(observableGroupBufferTimeMilliSeconds, observableGroupBufferSize)<br /> {<br /> _connectionString = connectionString;<br /> _logTableName = logTableName;<br /> _roleId = RoleEnvironment.CurrentRoleInstance.Id;<br /> }<br /> <br /> public void Initialize()<br /> {<br /> try<br /> {<br /> CloudStorageAccount storageAccount = CloudStorageAccount.Parse(_connectionString);<br /> <br /> // Create the table client.<br /> CloudTableClient tableClient = storageAccount.CreateCloudTableClient();<br /> <br /> // Create the CloudTable object<br /> CloudTable table = tableClient.GetTableReference(_logTableName);<br /> table.CreateIfNotExists();<br /> }<br /> catch (Exception ex)<br /> {<br /> Trace.TraceError(ex.ToString());<br /> }<br /> }<br /> <br /> public override async Task&lt;int&gt; Log(IEnumerable&lt;LogMessage&gt; messages)<br /> {<br /> int totalCounter = 0;<br /> try<br /> {<br /> <br /> CloudStorageAccount storageAccount = CloudStorageAccount.Parse(_connectionString);<br /> <br /> // Create the table client.<br /> CloudTableClient tableClient = storageAccount.CreateCloudTableClient();<br /> <br /> // Create the CloudTable object that represents the "people" table.<br /> CloudTable logTable = tableClient.GetTableReference(_logTableName);<br /> <br /> var batchOperation = new TableBatchOperation();<br /> int counter = 0;<br /> foreach (var message in messages)<br /> {<br /> var tableLogMessage = new TableLogMessage(message) {RoleId = _roleId};<br /> batchOperation.Insert(tableLogMessage);<br /> counter++;<br /> <br /> if (counter &lt; MaxTableInsertBatchSize)<br /> continue;<br /> <br /> await logTable.ExecuteBatchAsync(batchOperation);<br /> totalCounter += counter;<br /> batchOperation = new TableBatchOperation();<br /> counter = 0;<br /> }<br /> <br /> if (counter &gt; 0)<br /> {<br /> await logTable.ExecuteBatchAsync(batchOperation);<br /> totalCounter += counter;<br /> }<br /> <br /> <br /> <br /> <br /> <br /> }<br /> catch (Exception ex)<br /> {<br /> Trace.TraceError(ex.ToString());<br /> <br /> if (ex is Microsoft.WindowsAzure.Storage.StorageException)<br /> {<br /> var storageEx = ex as Microsoft.WindowsAzure.Storage.StorageException;<br /> if (storageEx.RequestInformation.HttpStatusCode == 404)<br /> {<br /> Initialize();<br /> }<br /> <br /> }<br /> // our example uses all or nothing with risk of getting duplicates<br /> return 0;<br /> }<br /> <br /> return totalCounter;<br /> }<br /> }</code></pre>
<p>Let&rsquo;s demonstrate how to use it in a simple project.&nbsp; First create a new WorkerRole cloud project.&nbsp; Use NuGet to add the Rx-Main and Azure Storage packages.</p>
<p>Add the following Configuration Settings to ServiceDefinition.csdef:</p>
<pre class="scroll"><code class="html"> &lt;ConfigurationSettings&gt;<br /> &lt;Setting name="ServiceBus1"/&gt;<br /> &lt;Setting name="ServiceBus2"/&gt;<br /> &lt;Setting name="AzureStorage"/&gt;<br /> &lt;/ConfigurationSettings&gt;</code></pre>
<p>Next in your Cloud.cscfg file fill in the new settings:</p>
<pre class="scroll"><code class="html"> &lt;Instances count="4" /&gt;<br /> &lt;ConfigurationSettings&gt;<br /> &lt;Setting name="AzureStorage" value="[Your Value]" /&gt;<br /> &lt;Setting name="ServiceBus1" value="[Your Value]" /&gt;<br /> &lt;Setting name="ServiceBus2" value="[Your Value]" /&gt;<br /> &lt;/ConfigurationSettings&gt;</code></pre>
<p>Now let&rsquo;s configure our topics and subscriptions:</p>
<p>&nbsp; &nbsp;</p>
<pre class="scroll"><code class="csharp"> public class SimplifiedServiceBusHelper<br /> {<br /> private int messageLockDuration = 300000;<br /> private string _serviceBusConnectionString;<br /> private string[] _topics;<br /> public SimplifiedServiceBusHelper(string serviceBusConnectionString, IEnumerable&lt;string&gt; topics)<br /> {<br /> _serviceBusConnectionString = serviceBusConnectionString;<br /> _topics = topics.ToArray();<br /> }<br /> <br /> public TopicClient[] TopicClients { get; private set; }<br /> public SubscriptionClient[] SubscriptionClients { get; private set; }<br /> <br /> public void Setup(string template = "subscriptionontopic{0}")<br /> {<br /> TopicClients = new TopicClient[_topics.Length];<br /> SubscriptionClients = new SubscriptionClient[_topics.Length];<br /> <br /> for(int i=0; i&lt; _topics.Length;i++)<br /> {<br /> var topic = _topics[i];<br /> var namespaceManager = NamespaceManager.CreateFromConnectionString(_serviceBusConnectionString);<br /> if (!namespaceManager.TopicExists(topic))<br /> {<br /> namespaceManager.CreateTopic(<br /> new TopicDescription(topic)<br /> {<br /> DefaultMessageTimeToLive = TimeSpan.MaxValue,<br /> EnableBatchedOperations = true,<br /> <br /> RequiresDuplicateDetection = true,<br /> MaxSizeInMegabytes = 5120,<br /> EnablePartitioning = true<br /> });<br /> }<br /> TopicClients[i] = TopicClient.CreateFromConnectionString(_serviceBusConnectionString, topic);<br /> <br /> var subscription = string.Format(template, topic);<br /> if (!namespaceManager.SubscriptionExists(topic, subscription))<br /> {<br /> namespaceManager.CreateSubscription(new SubscriptionDescription(topic, subscription)<br /> {<br /> DefaultMessageTimeToLive = TimeSpan.MaxValue,<br /> MaxDeliveryCount = 10000,<br /> EnableBatchedOperations = true,<br /> EnableDeadLetteringOnFilterEvaluationExceptions = false,<br /> EnableDeadLetteringOnMessageExpiration = false,<br /> LockDuration = TimeSpan.FromMilliseconds(messageLockDuration),<br /> });<br /> }<br /> SubscriptionClients[i] = SubscriptionClient.CreateFromConnectionString(_serviceBusConnectionString,<br /> topic, subscription);<br /> <br /> <br /> var subClient = SubscriptionClient.CreateFromConnectionString(_serviceBusConnectionString,<br /> topic, subscription);<br /> <br /> <br /> <br /> }<br /> }<br /> <br /> <br /> <br /> }<br /> }</code></pre>
<p>&nbsp;</p>
<p>That we can modify our WorkerRole.cs class as follows:</p>
<p>&nbsp;</p>
<pre class="scroll"><code class="csharp"> public class WorkerRole : RoleEntryPoint<br /> {<br /> private ConcreteMessageObserver _observer;<br /> <br /> public override void Run()<br /> {<br /> // This is a sample worker implementation. Replace with your logic.<br /> Trace.TraceInformation("WorkerRole1 entry point called");<br /> Setup();<br /> <br /> while (true)<br /> {<br /> Thread.Sleep(10000);<br /> if (_observer != null &amp;&amp; _observer.Logged &gt; 0)<br /> {<br /> Trace.TraceInformation("observer.Logged: {0}", _observer.Logged);<br /> }<br /> }<br /> }<br /> <br /> private void Setup()<br /> {<br /> string serviceBusConnectionString = RoleEnvironment.GetConfigurationSettingValue("ServiceBus1");<br /> string serviceBusConnectionString2 = RoleEnvironment.GetConfigurationSettingValue("ServiceBus2");<br /> <br /> var allTopics = new string[] { "topic0", "topic1", "topic2", "topic3", "topic4", "topic5", "topic6", "topic7", };<br /> int num = RoleEnvironment.CurrentRoleInstance.Id.Last() - '0';<br /> var topics = new string[] { allTopics[num * 2], allTopics[num * 2 + 1] };<br /> var sbHelper1 = new SimplifiedServiceBusHelper(serviceBusConnectionString, topics);<br /> var sbHelper2 = new SimplifiedServiceBusHelper(serviceBusConnectionString2, topics);<br /> sbHelper1.Setup();<br /> sbHelper2.Setup();<br /> IEnumerable&lt;SubscriptionClient&gt; subClients = sbHelper1.SubscriptionClients.Union(sbHelper2.SubscriptionClients);<br /> SubscriptionClientsToObservableHelper&lt;LogMessage&gt; helper = new SubscriptionClientsToObservableHelper&lt;LogMessage&gt;(subClients, 100);<br /> <br /> string storageConnectionString = RoleEnvironment.GetConfigurationSettingValue("AzureStorage");<br /> _observer = new ConcreteMessageObserver(15000, 100, storageConnectionString);<br /> _observer.Initialize();<br /> _observer.Start(helper);<br /> }<br /> <br /> <br /> public override bool OnStart()<br /> {<br /> // Set the maximum number of concurrent connections <br /> ServicePointManager.DefaultConnectionLimit = 5000;<br /> <br /> // For information on handling configuration changes<br /> // see the MSDN topic at https://go.microsoft.com/fwlink/?LinkId=166357.<br /> <br /> return base.OnStart();<br /> }<br /> }</code></pre>
<p>Let&rsquo;s create a simple console program to populate the Service Bus:</p>
<p>&nbsp;</p>
<pre class="scroll"><code class="csharp"> class Program<br /> {<br /> <br /> private static string _sb1 = "[Fill Me]";<br /> private static string _sb2 = "[Fill Me]";<br /> private static string[] allTopics = new string[] { "topic0", "topic1", "topic2", "topic3", "topic4", "topic5", "topic6", "topic7", };<br /> static void Main(string[] args)<br /> {<br /> int numSessions = 10000;<br /> int numMessages = 20;<br /> <br /> var sbHelper1 = new SimplifiedServiceBusHelper(_sb1, allTopics);<br /> var sbHelper2 = new SimplifiedServiceBusHelper(_sb2, allTopics);<br /> sbHelper1.Setup();<br /> sbHelper2.Setup();<br /> <br /> var topicClients = sbHelper1.TopicClients.Union(sbHelper2.TopicClients).ToArray();<br /> var logMessages = new List&lt;LogMessage&gt;();<br /> <br /> for (int i = 0; i &lt; numSessions; i++)<br /> {<br /> var sessionId = Guid.NewGuid().ToString();<br /> for (int j = 0; j &lt; numMessages; j++)<br /> {<br /> <br /> <br /> var msg = new LogMessage()<br /> {<br /> SessionId = sessionId,<br /> LogData = "Message #"+j,<br /> LogMessageId = Guid.NewGuid().ToString(),<br /> TimeStamp = DateTime.Now.AddSeconds(j),<br /> };<br /> <br /> logMessages.Add(msg);<br /> }<br /> }<br /> <br /> var countQuery = from m in logMessages<br /> group m by (Math.Abs(m.SessionId.GetHashCode()) % topicClients.Length)<br /> into g<br /> select new { Client = topicClients[g.Key], Count = g.Count() };<br /> <br /> <br /> var query = from m in logMessages<br /> group m by ( Math.Abs(m.SessionId.GetHashCode()) % topicClients.Length)<br /> into g<br /> select new {Client = topicClients[g.Key], Messages = g.ToList()};<br /> <br /> var t = from g in query<br /> select Task.Run(async () =&gt;<br /> {<br /> int counter = 0;<br /> var batch = new List&lt;BrokeredMessage&gt;();<br /> foreach (var msg in g.Messages)<br /> {<br /> var json = Newtonsoft.Json.JsonConvert.SerializeObject(msg);<br /> var brokeredMessage = new BrokeredMessage(json);<br /> batch.Add(brokeredMessage);<br /> counter++;<br /> <br /> if (counter == 100)<br /> {<br /> while (true)<br /> {<br /> try<br /> {<br /> await g.Client.SendBatchAsync(batch);<br /> break;<br /> }<br /> catch (Exception ex)<br /> {<br /> Trace.TraceError(ex.ToString());<br /> }<br /> await Task.Delay(2000);<br /> }<br /> counter = 0;<br /> batch = new List&lt;BrokeredMessage&gt;();<br /> }<br /> }<br /> <br /> if (counter &gt; 0)<br /> {<br /> while (true)<br /> {<br /> try<br /> {<br /> await g.Client.SendBatchAsync(batch);<br /> break;<br /> }<br /> catch (Exception ex)<br /> {<br /> Trace.TraceError(ex.ToString());<br /> }<br /> await Task.Delay(2000);<br /> }<br /> }<br /> <br /> <br /> });<br /> <br /> Task.WhenAll(t).Wait();<br /> <br /> return;<br /> <br /> <br /> <br /> }<br /> }</code></pre>
<p>Run your console program and then you can monitor the subscription.</p>
<p>&nbsp;<a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/4670.sub%20count.png" original-url="https://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/4670.sub-count.png"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/4670.sub%20count.png" original-url="https://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/4670.sub-count.png" alt="" border="0" /></a></p>
<p>&nbsp;</p>
<p>Publish your cloud service and then watch the numbers go down!&nbsp; You can verify that the log messages end up in table storage using storage explorer:</p>
<p>&nbsp;<a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/6740.log%20output%20to%20table%20storage.png" original-url="https://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/6740.log-output-to-table-storage.png"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/6740.log%20output%20to%20table%20storage.png" original-url="https://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/6740.log-output-to-table-storage.png" alt="" border="0" /></a></p>
<p>&nbsp;</p>
<p>Final Thoughts:</p>
<p>This was my first adventure with reactive extensions and the only thing I am sure of is that I still have a lot to learn.&nbsp; I will likely revisit this post as my experience with them matures.&nbsp; I certainly could have used Rx in the console app and I need to better understand Rx error handling.&nbsp; Despite my neophyte skill level I am still quite impressed with their flexibility.&nbsp;&nbsp;</p>
<p>&nbsp;</p>]]></content:encoded>
    </item>
    <item>
      <title>Capturing SignalR 2.0 Performance Counters in Azure using Windows Azure Diagnostics (WAD)</title>
      <link>https://docs.microsoft.com/archive/blogs/mgrichard/capturing-signalr-2-0-performance-counters-in-azure-using-windows-azure-diagnostics-wad</link>
      <pubDate>Tue, 21 Jan 2014 08:15:00 GMT</pubDate>
      <dc:creator><![CDATA[Martin Richard]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/mgrichard/2014/01/21/capturing-signalr-2-0-performance-counters-in-azure-using-windows-azure-diagnostics-wad/</guid>
      <description><![CDATA[SignalR&nbsp;is a new library for ASP.NET developers that eases development of real-time web...]]></description>
      <content:encoded><![CDATA[<p><a href="https://www.asp.net/signalr">SignalR</a>&nbsp;is a new library for ASP.NET developers that eases development of real-time web functionality by enabling bi-directional communication between server and client. Azure is a convenient way for customers to host scalable web applications and makes a convenient vehicle for your SignalR applications. Windows Azure Diagnostics can be configured to capture diagnostic information to Azure storage. In this short tutorial, we examine how to leverage Windows Azure Diagnostics to capture performance counters specific to SignalR.</p>
<h2>Here are the steps to get a working SignalR web application that utilizes Windows Azure Diagnostics to capture SignalR performance counter data:</h2>
<ol>
<li>
<h3>Create a new .NET ASP.NET MVC Web Role Cloud application:</h3>
<div><a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/2313.image001.jpg" original-url="https://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/2313.image001.jpg"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/2313.image001.jpg" original-url="https://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/2313.image001.jpg" alt="" border="0" /></a></div>
<div><br /> <a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/6201.image002.jpg" original-url="https://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/6201.image002.jpg"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/6201.image002.jpg" original-url="https://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/6201.image002.jpg" alt="" border="0" /></a></div>
<div><br /><a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/0576.image003.jpg" original-url="https://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/0576.image003.jpg"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/0576.image003.jpg" original-url="https://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/0576.image003.jpg" alt="" border="0" /></a></div>
</li>
<li>
<h3>Open up diagnostics.wadcfg in your Azure project and replace its contents with the following configuration information:</h3>
<div style="font-size: 12px;"><a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/7041.image004.png" original-url="https://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/7041.image004.png"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/7041.image004.png" original-url="https://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/7041.image004.png" alt="" border="0" /></a></div>
<div style="font-size: 12px;">
<p>&nbsp;</p>
<p>&lt;?xml version="1.0"?&gt;</p>
<p>&lt;!-- Specifies the interval at which the diagnostics agent polls the storage service for diagnostics configuration&nbsp;<span style="font-size: 12px;">changes--&gt;</span></p>
<p>&lt;DiagnosticMonitorConfiguration xmlns="https://schemas.microsoft.com/ServiceHosting/2010/10/DiagnosticsConfiguration"&nbsp;<span style="font-size: 12px;">configurationChangePollInterval="PT1M" overallQuotaInMB="4096"&gt;</span></p>
<p style="padding-left: 30px;">&nbsp; &lt;!--Configure the diagnostics infrastructure logs into which the diagnostics agent log data about itself --&gt;</p>
<p style="padding-left: 30px;">&nbsp; &lt;DiagnosticInfrastructureLogs bufferQuotaInMB="256"&nbsp;<span style="font-size: 12px;">&nbsp;scheduledTransferLogLevelFilter="Warning"&nbsp;</span><span style="font-size: 12px;">&nbsp; scheduledTransferPeriod="PT5M" /&gt;</span></p>
<p style="padding-left: 30px;">&nbsp; &lt;!-- Configure the capture and persistence of basic log from the WAD trace listener--&gt;</p>
<p style="padding-left: 30px;">&nbsp; &lt;Logs bufferQuotaInMB="100" scheduledTransferLogLevelFilter="Warning" scheduledTransferPeriod="PT5M" /&gt;</p>
<p style="padding-left: 30px;">&nbsp; &lt;!-- Configure the capture and persistence of data located in directories on local file system&nbsp;<span style="font-size: 12px;">&nbsp;which are for crash dupmp, failed IIS request logs and IIS logs--&gt;</span></p>
<p style="padding-left: 30px;">&nbsp; &lt;Directories bufferQuotaInMB="1024" scheduledTransferPeriod="PT5M"&gt;</p>
<p style="padding-left: 30px;">&nbsp;&nbsp;&nbsp; &lt;CrashDumps container="wad-crash-dumps" directoryQuotaInMB="128" /&gt;</p>
<p style="padding-left: 30px;">&nbsp;&nbsp;&nbsp; &lt;FailedRequestLogs container="wad-frq" directoryQuotaInMB="128"/&gt;</p>
<p style="padding-left: 30px;">&nbsp;&nbsp;&nbsp; &lt;IISLogs container="wad-iis" directoryQuotaInMB="128" /&gt;</p>
<p style="padding-left: 30px;">&nbsp;&nbsp;&nbsp; &lt;DataSources&gt;</p>
<p style="padding-left: 60px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;DirectoryConfiguration container="wad-startup" directoryQuotaInMB="10"&gt;</p>
<p style="padding-left: 60px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;!-- Absolute specifies an absolute path with optional environment expansion --&gt;</p>
<p style="padding-left: 60px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;Absolute expandEnvironment="true" path="%ROLEROOT%\approot\bin\Startup\Log" /&gt;</p>
<p style="padding-left: 60px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/DirectoryConfiguration&gt;</p>
<p style="padding-left: 30px;">&nbsp;&nbsp;&nbsp; &lt;/DataSources&gt;</p>
<p style="padding-left: 30px;">&nbsp; &lt;/Directories&gt;</p>
<p style="padding-left: 30px;">&nbsp; &lt;!-- Configure the capture and persistence of performance counters data--&gt;</p>
<p style="padding-left: 30px;">&nbsp; &lt;PerformanceCounters bufferQuotaInMB="256" scheduledTransferPeriod="PT5M"&gt;</p>
<p style="padding-left: 60px;">&nbsp;&nbsp;&nbsp; &lt;PerformanceCounterConfiguration&nbsp;<span style="font-size: 12px;">&nbsp;counterSpecifier="\Processor(_Total)\% Processor Time" sampleRate="PT10S" /&gt;</span></p>
<p style="padding-left: 60px;">&nbsp;&nbsp;&nbsp; &lt;PerformanceCounterConfiguration&nbsp;<span style="font-size: 12px;">&nbsp;counterSpecifier="\LogicalDisk(*)\% Free Space" sampleRate="PT10S" /&gt;</span></p>
<p style="padding-left: 60px;">&nbsp;&nbsp;&nbsp; &lt;PerformanceCounterConfiguration&nbsp;<span style="font-size: 12px;">&nbsp;counterSpecifier="\LogicalDisk(*)\Avg. Disk sec/Transfer" sampleRate="PT10S" /&gt;</span></p>
<p style="padding-left: 60px;">&nbsp;&nbsp;&nbsp; &lt;PerformanceCounterConfiguration&nbsp;<span style="font-size: 12px;">&nbsp;counterSpecifier="\Memory\Available Bytes" sampleRate="PT10S" /&gt;</span></p>
<p style="padding-left: 60px;">&nbsp;&nbsp;&nbsp; &lt;PerformanceCounterConfiguration&nbsp;<span style="font-size: 12px;">&nbsp;counterSpecifier="\ASP.NET\Requests Queued" sampleRate="PT10S" /&gt;</span></p>
<p style="padding-left: 60px;">&nbsp;&nbsp;&nbsp; &lt;PerformanceCounterConfiguration&nbsp;<span style="font-size: 12px;">&nbsp;counterSpecifier="\ASP.NET\Requests Rejected" sampleRate="PT10S" /&gt;</span></p>
<p style="padding-left: 60px;">&nbsp;&nbsp;&nbsp; &lt;PerformanceCounterConfiguration &nbsp;<span style="font-size: 12px;">counterSpecifier="\ASP.NET Applications(__Total__)\Requests/Sec" sampleRate="PT10S" /&gt;</span></p>
<p style="padding-left: 60px;">&nbsp;&nbsp;&nbsp; &lt;PerformanceCounterConfiguration&nbsp;<span style="font-size: 12px;">&nbsp;counterSpecifier="\ASP.NET\Request Wait Time" sampleRate="PT10S" /&gt;</span></p>
<p style="padding-left: 60px;">&nbsp;&nbsp;&nbsp; &lt;PerformanceCounterConfiguration&nbsp;<span style="font-size: 12px;">&nbsp;counterSpecifier="\.NET CLR Memory(w3wp)\% Time in GC" sampleRate="PT10S" /&gt;</span></p>
<p style="padding-left: 60px;">&nbsp;&nbsp;&nbsp; &lt;PerformanceCounterConfiguration&nbsp;<span style="font-size: 12px;">&nbsp;counterSpecifier="\.NET CLR Exceptions(w3wp)\# of Exceps Thrown / sec" sampleRate="PT10S" /&gt;</span></p>
<p style="padding-left: 60px;">&nbsp;&nbsp;&nbsp; &lt;PerformanceCounterConfiguration&nbsp;<span style="font-size: 12px;">&nbsp;counterSpecifier="\.NET CLR LocksAndThreads(w3wp)\# of current logical Threads" sampleRate="PT10S" /&gt;</span></p>
<p style="padding-left: 60px;">&nbsp;&nbsp;&nbsp; &lt;PerformanceCounterConfiguration&nbsp;<span style="font-size: 12px;">&nbsp;counterSpecifier="\.NET CLR LocksAndThreads(w3wp)\# of current physical Threads" sampleRate="PT10S" /&gt;</span></p>
<p style="padding-left: 60px;">&nbsp;&nbsp;&nbsp; &lt;PerformanceCounterConfiguration&nbsp;<span style="font-size: 12px;">&nbsp;counterSpecifier="\.NET CLR LocksAndThreads(w3wp)\Current Queue Length" sampleRate="PT10S" /&gt;</span></p>
<p style="padding-left: 60px;">&nbsp;&nbsp;&nbsp; &lt;PerformanceCounterConfiguration&nbsp;<span style="font-size: 12px;">&nbsp;counterSpecifier="\.NET CLR LocksAndThreads(w3wp)\Contention Rate / sec" sampleRate="PT10S" /&gt;</span></p>
<p style="padding-left: 60px;">&nbsp;&nbsp;&nbsp; &lt;PerformanceCounterConfiguration&nbsp;<span style="font-size: 12px;">&nbsp;counterSpecifier="\.NET CLR Memory(w3wp)\# Bytes in all Heaps" sampleRate="PT10S" /&gt;</span></p>
<p style="padding-left: 60px;">&nbsp;&nbsp;&nbsp; &lt;PerformanceCounterConfiguration&nbsp;<span style="font-size: 12px;">&nbsp;counterSpecifier="\.NET CLR Memory(w3wp)\# GC Handles" sampleRate="PT10S" /&gt;</span></p>
<p style="padding-left: 60px;">&nbsp;&nbsp;&nbsp; &lt;PerformanceCounterConfiguration&nbsp;<span style="font-size: 12px;">&nbsp;counterSpecifier="\.NET CLR Memory(w3wp)\# of Pinned Objects" sampleRate="PT10S" /&gt;</span></p>
<p style="padding-left: 60px;">&nbsp;&nbsp;&nbsp; &lt;PerformanceCounterConfiguration&nbsp;<span style="font-size: 12px;">&nbsp;counterSpecifier="\TCPV4\Segments Retransmitted/sec" sampleRate="PT10S" /&gt;</span></p>
<p style="padding-left: 60px;">&nbsp; &nbsp; &lt;PerformanceCounterConfiguration&nbsp;<span style="font-size: 12px;">&nbsp;counterSpecifier="\TCPV4\Connection Failures" sampleRate="PT10S" /&gt;</span></p>
<p style="padding-left: 60px;">&nbsp; &nbsp; &lt;PerformanceCounterConfiguration&nbsp;<span style="font-size: 12px;">&nbsp;counterSpecifier="\TCPV6\Segments Retransmitted/sec" sampleRate="PT10S" /&gt;</span></p>
<p style="padding-left: 60px;">&nbsp;&nbsp;&nbsp; &lt;!-- comment out to check that addition occured&nbsp;&lt;PerformanceCounterConfiguration&nbsp;<span style="font-size: 12px;">&nbsp;counterSpecifier="\TCPV6\Connection Failures" sampleRate="PT10S" /&gt;--&gt;</span></p>
<p style="padding-left: 30px;">&nbsp; &lt;/PerformanceCounters&gt;</p>
<p style="padding-left: 30px;">&nbsp; &lt;!-- configure the capture and persistence of the Windows event logs --&gt;</p>
<p style="padding-left: 30px;">&nbsp; &lt;WindowsEventLog bufferQuotaInMB="100" scheduledTransferLogLevelFilter="Warning" scheduledTransferPeriod="PT5M"&gt;</p>
<p style="padding-left: 60px;">&nbsp;&nbsp;&nbsp; &lt;DataSource name="System!*" /&gt;</p>
<p style="padding-left: 60px;"><span style="font-size: 12px;">&nbsp; &nbsp; &lt;DataSource name="Application!*"/&gt;</span></p>
<p style="padding-left: 30px;">&nbsp; &lt;/WindowsEventLog&gt;</p>
<p>&lt;/DiagnosticMonitorConfiguration&gt;</p>
</div>
<p>&nbsp;</p>
</li>
<li>
<div>
<h3>From the Tools\Library Package Manager open up the Package Manager Console. Use it to install SignalR 2.0:</h3>
<div><a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/2210.image005.jpg" original-url="https://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/2210.image005.jpg"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/2210.image005.jpg" original-url="https://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/2210.image005.jpg" alt="" border="0" /></a></div>
</div>
</li>
<li>
<div>
<h3>Install signalR utils:</h3>
<div><a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/7127.image006.png" original-url="https://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/7127.image006.png"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/7127.image006.png" original-url="https://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/7127.image006.png" alt="" border="0" /></a></div>
</div>
</li>
<li>
<div>
<h3>In the web project, create a startup folder and navigate to the packages folder to add the signalr.exe utility:</h3>
<div><a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/5305.image007.jpg" original-url="https://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/5305.image007.jpg"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/5305.image007.jpg" original-url="https://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/5305.image007.jpg" alt="" border="0" /></a></div>
</div>
</li>
<li>
<div>
<h3>Set the properties of the signalr.exe file to copy always:</h3>
<div><a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/2818.image008.png" original-url="https://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/2818.image008.png"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/2818.image008.png" original-url="https://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/2818.image008.png" alt="" border="0" /></a></div>
</div>
</li>
<li>
<div>
<h3>Create a new file within the startup directory called SignalrPerfCounterInstall.cmd with the following contents and close the file:</h3>
<div>
<pre>SET SignalR_LogDir=%~dp0Log\<br />MKDIR "%SignalR_LogDir%"<br />cd %~dp0<br />signalr.exe ipc   &gt;&gt; "%SignalR_LogDir%SignalR_Log.txt" 2&gt;&amp;1<br />net localgroup "Performance Monitor Users" "Network Service" /ADD &gt;&gt; "%SignalR_LogDir%NetworkAdd.txt" 2&gt;&amp;1</pre>
</div>
</div>
</li>
<li>
<div>
<h3>Set the properties of SignalrPerfCounterInstall.cmd to copy always:</h3>
<div><a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/0027.image009.png" original-url="https://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/0027.image009.png"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/0027.image009.png" original-url="https://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/0027.image009.png" alt="" border="0" /></a></div>
</div>
</li>
<li>
<div>
<h3>Open the file with the built in Binary Editor and remove the extraneous bits if present and save the file:</h3>
<div><a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/7558.image010.jpg" original-url="https://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/7558.image010.jpg"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/7558.image010.jpg" original-url="https://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/7558.image010.jpg" alt="" border="0" /></a> <a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/1258.image011.png" original-url="https://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/1258.image011.png"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/1258.image011.png" original-url="https://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/1258.image011.png" alt="" border="0" /></a></div>
</div>
</li>
<li>
<div>
<h3>Navigate to the .csdef file and add the following startup task within your web role:</h3>
<div>
<pre> &lt;Startup&gt;<br />      &lt;Task commandLine="startup\SignalrPerfCounterInstall.cmd"<br />          executionContext="elevated" taskType="background" /&gt;<br />  &lt;/Startup&gt;</pre>
</div>
</div>
</li>
<li>
<div>
<h3>Remove the jquery bundle from Shared/_Layout.cshtml:</h3>
<div><a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/6874.image012.png" original-url="https://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/6874.image012.png"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/6874.image012.png" original-url="https://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/6874.image012.png" alt="" border="0" /></a></div>
</div>
</li>
<li>
<div>
<h3>Replace your Home/index.html with the following:</h3>
<div>
<pre class="scroll"><code class="html">&lt;script src="~/Scripts/jquery-1.10.2.min.js"&gt;&lt;/script&gt;<br /> &lt;script src="~/Scripts/jquery.signalR-2.0.1.min.js"&gt;&lt;/script&gt;<br /> &lt;script src="~/signalr/hubs" type="text/javascript"&gt;&lt;/script&gt;<br /> <br /> &lt;div id="body"&gt;<br /> &lt;section class="featured"&gt;<br /> &lt;div class="content-wrapper"&gt;<br /> &lt;p&gt;<br /> Hello World!<br /> &lt;/p&gt;<br /> &lt;div style="font-size:large;"&gt;<br /> My Counter: &lt;span id="counter"&gt;&lt;/span&gt;<br /> &lt;/div&gt;<br /> &lt;/div&gt;<br /> &lt;/section&gt;<br /> &lt;section class="content-wrapper main-content clear-fix"&gt;&lt;/section&gt;<br /> &lt;/div&gt;<br /> <br /> <br /> <br /> <br /> &lt;script type="text/javascript"&gt;<br /> $(document).ready(function () {<br /> <br /> var hub = $.connection.myHub;<br /> <br /> <br /> hub.client.sendResult = function (x) {<br /> console.log('sendResult(' + x + ')');<br /> $("#counter").text(x);<br /> window.setTimeout(function () {<br /> hub.server.increment(x);<br /> }, 1000);<br /> <br /> };<br /> <br /> <br /> <br /> $.connection.hub.connected = function () {<br /> };<br /> $.connection.hub.disconnected = function () {<br /> };<br /> <br /> <br /> <br /> <br /> $.connection.hub.stateChanged(function (change) {<br /> console.log('new State' + change.newState);<br /> if (change.newState === $.signalR.connectionState.disconnected) {<br /> $.connection.hub.start();<br /> }<br /> if (change.newState === $.signalR.connectionState.reconnecting) {<br /> console.log('Re-connecting');<br /> <br /> } else if (change.newState === $.signalR.connectionState.connected) {<br /> console.log('The server is online');<br /> }<br /> });<br /> <br /> $.connection.hub.error(function (error) {<br /> console.log('error ' + error);<br /> });<br /> $.connection.hub.logging = true;<br /> $.connection.hub.reconnected(function () {<br /> console.log('Reconnected');<br /> hub.server.increment(0);<br /> });<br /> <br /> $.connection.hub.start().done(function () {<br /> console.log('hub started');<br /> hub.server.increment(0);<br /> });<br /> <br /> });<br /> <br /> &lt;/script&gt;<br /> </code></pre>
</div>
</div>
</li>
<li>
<div>
<h3>Now create a Hubs folder and add the MyHub class as follows:</h3>
<div>
<pre class="scroll"><code class="csharp">namespace WebRole1.Hubs<br /> {<br /> <br /></code></pre>
<blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><code class="csharp"> using Microsoft.AspNet.SignalR;<br /></code><code class="csharp"> using System;<br /></code><code class="csharp"> using System.Collections.Generic;<br /></code><code class="csharp"> using System.Linq;<br /></code><code class="csharp"> using System.Threading.Tasks;<br /></code><code class="csharp"> using System.Web;<br /></code><code class="csharp"> <br /></code><code class="csharp"> public class MyHub : Hub<br /></code><code class="csharp"> {<br /></code>
<blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><code class="csharp"> public async Task Increment(int x)<br /></code><code class="csharp"> {<br /></code>
<blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><code class="csharp"> await this.Clients.Caller.sendResult(x+1);</code></blockquote>
<code class="csharp"> }</code></blockquote>
<code class="csharp"> </code></blockquote>
<blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><code class="csharp"> }</code></blockquote>
<pre class="scroll"><code class="csharp"> }<br /> </code></pre>
</div>
</div>
</li>
<li>
<div>
<h3>Crank.exe (a test tool to generate load discussed below) requires a PersistentConnection so add a PersistentConnectionFolder and add the MyPersistentConnection class as follows:</h3>
<div>
<pre class="scroll"><code class="csharp">using System.Threading.Tasks;<br />using Microsoft.AspNet.SignalR;<br />using Microsoft.AspNet.SignalR.Infrastructure;<br /> <br /></code><span style="font-size: 12px;"> public class MyPersistentConnection : PersistentConnection</span><br /><span style="font-size: 12px;"> {</span></pre>
<blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><span style="font-size: 12px;"> protected override Task OnReceived(IRequest request, </span><span style="font-size: 12px;"> string connectionId, </span><span style="font-size: 12px;"> string data)<br /></span><span style="font-size: 12px;"> {<br /></span>
<blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><span style="font-size: 12px;"> // Broadcast data to all clients<br /></span><span style="font-size: 12px;"> return Connection.Broadcast(data);</span></blockquote>
<span style="font-size: 12px;"> }</span></blockquote>
<pre class="scroll"><code class="csharp"> }<br /></code><code class="csharp"> </code></pre>
</div>
</div>
</li>
<li>
<div>
<h3>SignalR 2.0 requires Owin so add the following Startup.cs object:</h3>
<div>
<pre class="scroll"><code class="csharp">namespace WebRole1<br /> {<br /></code></pre>
<blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><code class="csharp"> using Microsoft.AspNet.SignalR;<br /></code><code class="csharp"> using Microsoft.Owin;<br /></code><code class="csharp"> using WebRole1.PersistentConnection;<br /></code><code class="csharp"> using Owin;<br /></code><code class="csharp"> <br /></code><code class="csharp"> public class Startup<br /></code><code class="csharp"> {<br /></code>
<blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><code class="csharp"> public void Configuration(IAppBuilder app)<br /></code><code class="csharp"> {<br /></code>
<blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><code class="csharp"> // Map all hubs to "/signalr"<br /></code><code class="csharp"> app.MapSignalR();<br /></code><code class="csharp"> // Map the Echo PersistentConnection to "/echo"<br /></code><code class="csharp"> app.MapSignalR&lt;MyPersistentConnection&gt;("/broad");</code></blockquote>
<code class="csharp"> } </code></blockquote>
<code class="csharp"> }</code></blockquote>
<pre class="scroll"><code class="csharp"> }<br /> </code></pre>
</div>
</div>
</li>
<li>
<h3>Diagnostics is started before role startup so we need to dynamically add the performance counters. Furthermore, they are process specific so it is imperative that we dynamically register them from within Global.asax.cs. The following is a bit hokey but works:</h3>
<div>&nbsp;</div>
<h4>First create a SignalRDiagnosticHelper.cs class:</h4>
<div>
<pre class="scroll"><code class="csharp">namespace WebRole1.SignalRHelper<br /> {<br /> <br /> using System;<br /> using System.Linq;<br /> using Microsoft.WindowsAzure.Diagnostics;<br /> using Microsoft.WindowsAzure.Diagnostics.Management;<br /> using Microsoft.WindowsAzure.ServiceRuntime;<br /> <br /> public class SignalRDiagnosticHelper<br /> {<br /></code></pre>
<blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><code class="csharp"> private const string EmulatorCategoryTemplate = "signalr({0}_web)";<br /></code><code class="csharp"> private const string CloudCategoryTemplate = "signalr({0}_in_{1}_web)";<br /></code><code class="csharp"> <br /></code><code class="csharp"> public static string creatSignalRCategoryName()<br /></code><code class="csharp"> {<br /></code>
<blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><code class="csharp"> if (RoleEnvironment.IsEmulated)<br /></code><code class="csharp"> {<br /></code><code class="csharp"> string id = RoleEnvironment.CurrentRoleInstance.Id.ToLower();<br /></code><code class="csharp"> return string.Format(EmulatorCategoryTemplate, id);<br /></code><code class="csharp"> }<br /></code><code class="csharp"> else<br /></code><code class="csharp"> {<br /></code><code class="csharp"> var name = RoleEnvironment.CurrentRoleInstance.Role.Name.ToLower();<br /></code><code class="csharp"> var number = RoleEnvironment.CurrentRoleInstance.Id.Split(new char[] { '_' }).Last();<br /></code><code class="csharp"> return string.Format(CloudCategoryTemplate, name, number);<br /></code><code class="csharp"> <br /></code><code class="csharp"> }<br /></code><code class="csharp"> <br /></code></blockquote>
<span style="font-size: 12px;">}</span><br />
<blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><code class="csharp"> <br /></code></blockquote>
<span style="font-size: 12px;"> public static void RegisterSignalRPerfCounters()</span><br /><span style="font-size: 12px;"> {</span><br />
<blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><span style="font-size: 12px;"> TimeSpan ts = new TimeSpan(0, 0, 10);</span><br /><br /><span style="font-size: 12px;"> RoleInstanceDiagnosticManager roleInstanceDiagnosticManager =</span><br /><span style="font-size: 12px;"> new RoleInstanceDiagnosticManager(</span><br /><span style="font-size: 12px;"> RoleEnvironment.GetConfigurationSettingValue("Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString"),</span><br /><span style="font-size: 12px;"> RoleEnvironment.DeploymentId,</span><br /><span style="font-size: 12px;"> RoleEnvironment.CurrentRoleInstance.Role.Name,</span><br /><span style="font-size: 12px;"> RoleEnvironment.CurrentRoleInstance.Id);</span><br /><br /><span style="font-size: 12px;"> // Get the current diagnostic monitor for the role.</span><br /><span style="font-size: 12px;"> var config = roleInstanceDiagnosticManager.GetCurrentConfiguration() ?? DiagnosticMonitor.GetDefaultInitialConfiguration();</span><br /><br /><br /><span style="font-size: 12px;"> string connectionString = RoleEnvironment.GetConfigurationSettingValue("Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString");</span><br /><span style="font-size: 12px;"> string deploymentID = RoleEnvironment.DeploymentId;</span><br /><span style="font-size: 12px;"> string roleName = RoleEnvironment.CurrentRoleInstance.Role.Name;</span><br /><br /><span style="font-size: 12px;"> // Get the DeploymentDiagnosticManager object for your deployment.</span><br /><span style="font-size: 12px;"> DeploymentDiagnosticManager diagManager = new DeploymentDiagnosticManager(connectionString, deploymentID);</span><br /><br /><span style="font-size: 12px;"> var signalRCategoryName = creatSignalRCategoryName();</span><br /><br /><span style="font-size: 12px;"> RegisterCounter("Connections Connected", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Connections Reconnected", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Connections Disconnected", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Connections Current", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Connection Messages Received Total", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Connection Messages Sent Total", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Connection Messages Received/Sec", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Connection Messages Sent/Sec", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Message Bus Messages Received Total", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Message Bus Messages Received/Sec", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Scaleout Message Bus Messages Received/Sec", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Message Bus Messages Published Total", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Message Bus Messages Published/Sec", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Message Bus Subscribers Current", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Message Bus Subscribers Total", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Message Bus Subscribers/Sec", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Message Bus Allocated Workers", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Message Bus Busy Workers", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Message Bus Topics Current", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Errors: All Total", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Errors: All/Sec", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Errors: Hub Resolution Total", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Errors: Hub Resolution/Sec", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Errors: Hub Invocation Total", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Errors: Hub Invocation/Sec", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Errors: Tranport Total", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Errors: Transport/Sec", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Scaleout Streams Total", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Scaleout Streams Open", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Scaleout Streams Buffering", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Scaleout Errors Total", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Scaleout Errors/Sec", ts, signalRCategoryName, config);</span><br /><span style="font-size: 12px;"> RegisterCounter("Scaleout Send Queue Length", ts, signalRCategoryName, config);</span><br /><br /><span style="font-size: 12px;"> // useful for checking that it is not the category name that is issue...</span><br /><span style="font-size: 12px;"> RegisterCounter("Connection Failures", ts, "TCPV6", config);</span><br /><br /><br /><span style="font-size: 12px;"> // Apply the updated configuration to the diagnostic monitor. </span><br /><span style="font-size: 12px;"> DiagnosticMonitor.Start("Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString", config);</span><br /><code class="csharp"> return;</code></blockquote>
<code class="csharp"> }<br /></code><code class="csharp"> <br /></code><code class="csharp"> public static void RegisterCounter(string counterType, TimeSpan sampleRate, string category, DiagnosticMonitorConfiguration config)<br /></code><code class="csharp"> {<br /></code>
<blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><code class="csharp"> var counterSpecifier = "\\" + category + "\\" + counterType;<br /></code><code class="csharp"> config.PerformanceCounters.DataSources.Add(new PerformanceCounterConfiguration()<br /></code><code class="csharp"> {<br /></code><code class="csharp"> CounterSpecifier = counterSpecifier,<br /></code><code class="csharp"> SampleRate = sampleRate<br /></code><code class="csharp"> });</code></blockquote>
<code class="csharp"> }</code></blockquote>
<pre class="scroll"><code class="csharp"> }<br /> <br /> }</code></pre>
</div>
<h4>Next we must call this from within Application_Start() within global.asax.cs as follows:</h4>
<div>
<pre class="scroll">&nbsp;</pre>
<pre class="scroll"><code class="csharp">public class WebApiApplication : System.Web.HttpApplication<br /> {<br /></code></pre>
<blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><code class="csharp"> protected void Application_Start()<br /></code><code class="csharp"> {<br /></code>
<blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;"><code class="csharp"> SignalRHelper.SignalRDiagnosticHelper.RegisterSignalRPerfCounters();<br /></code><code class="csharp"> AreaRegistration.RegisterAllAreas();<br /></code><code class="csharp"> <br /></code><code class="csharp"> WebApiConfig.Register(GlobalConfiguration.Configuration);<br /></code><code class="csharp"> FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);<br /></code><code class="csharp"> RouteConfig.RegisterRoutes(RouteTable.Routes);<br /></code><code class="csharp"> BundleConfig.RegisterBundles(BundleTable.Bundles);</code></blockquote>
<code class="csharp"> }</code></blockquote>
<pre class="scroll"><code class="csharp"> }<br /> </code></pre>
<pre class="scroll"><code class="csharp"><br /> </code></pre>
</div>
</li>
<li>
<div>
<h3>Navigate to the web.config file and configure the Owin startup class within appSettings:</h3>
<div>
<p>&lt;add key="owin:AppStartup" value="WebRole1.Startup, WebRole1" /&gt;</p>
</div>
</div>
</li>
<li>
<h3>Replace the system.diagnostics element from web.config with the following to copy over event log data as well:</h3>
<div>
<p>&nbsp;</p>
<span style="font-size: 12px;">&lt;system.diagnostics&gt;</span><br /><span style="font-size: 12px;">&nbsp; &nbsp; &lt;trace&gt;</span><br /><span style="font-size: 12px;">&nbsp; &nbsp; &nbsp; &lt;listeners&gt;</span><br /><span style="font-size: 12px;">&nbsp; &nbsp; &nbsp; &nbsp; &lt;add type="Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitorTraceListener, Microsoft.WindowsAzure.Diagnostics, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" name="AzureDiagnostics"&gt;</span><br /><span style="font-size: 12px;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &lt;filter type="" /&gt;</span><br /><span style="font-size: 12px;">&nbsp; &nbsp; &nbsp; &nbsp; &lt;/add&gt;</span><br /><span style="font-size: 12px;">&nbsp; &nbsp; &nbsp; &nbsp;</span><span style="font-size: 12px;">&nbsp;&lt;/listeners&gt;</span><br /><span style="font-size: 12px;">&nbsp; &nbsp; &lt;/trace&gt;</span><br />&nbsp; &lt;/system.diagnostics&gt;
<p>&nbsp;</p>
</div>
</li>
<li>
<div>
<h3>We are almost done! Navigate to the cloud.cscfg file and add an azure storage account to the following setting:</h3>
<div><a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/3122.image013.jpg" original-url="https://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/3122.image013.jpg"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/3122.image013.jpg" original-url="https://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/3122.image013.jpg" alt="" border="0" /></a></div>
</div>
</li>
<li>
<div>
<h3>Deploy your application.</h3>
<div>&nbsp;</div>
</div>
</li>
<li>
<h3>Navigate to your deployed application. You should receive a Hello World Message and see a counter increasing:</h3>
<div><a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/4073.web%20page.PNG" original-url="https://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/4073.web-page.PNG"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/4073.web%20page.PNG" original-url="https://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/4073.web-page.PNG" alt="" border="0" /></a></div>
</li>
<li>
<div>
<h3>You can view your Perf Counter data using the excellent utility from <a href="https://www.cerebrata.com/?r=ADMv22.0.10.461">Cerebrata</a> called Azure Diagnostics Manager. They do have a trial available prior to purchase. Once you have installed the program and added your storage account you can click on the Performance Counters Tab and see SignalR Perf Counter Data from your Azure Web Roles:</h3>
<div><a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/6874.image015.jpg" original-url="https://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/6874.image015.jpg"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/6874.image015.jpg" original-url="https://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/6874.image015.jpg" alt="" border="0" /></a></div>
</div>
</li>
<li>
<div>
<h3>Note if you run into issues you should set up Remote Desktop Access and verify that the startup scripts executed by checking for the logs:</h3>
<div><a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/6114.image016.jpg" original-url="https://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/6114.image016.jpg"><img src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/63/15/6114.image016.jpg" original-url="https://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-01-63-15/6114.image016.jpg" alt="" border="0" /></a></div>
</div>
</li>
</ol>
<p>That's it! If you want to easily generate additional load you should download the<a href="https://github.com/SignalR/SignalR/tree/dev/src/Microsoft.AspNet.SignalR.Crank"> Crank</a> tool built by the SignalR team.</p>]]></content:encoded>
    </item>
  </channel>
</rss>