0 Comments Posted in:

Application Insights is a great service for finding out what is going on in your Azure Web Apps (or Function Apps). And to instrument a .NET or .NET Core application, you only need to enable a few Application Settings on your Web App. In this post I'll show what you need to set up.

I'll be using the Azure CLI to create and configure the resources, but you could just as easily use Azure PowerShell, or put all these settings in an ARM template (I'll show a sample later).

1 - Create a Web App and Application Insights

First of all, let's create our resource group which will contain an Application Insights instance, an App Service Plan and a Web App...

# create a resource group
$resourceGroup = "AppInsightsDemo"
$location = "westeurope"
az group create -n $resourceGroup -l $location

$suffix = "uieynsd"
# create an application insights instance
$appInsightsName = "app-insights-$suffix" 
az resource create `
  -g $resourceGroup -n $appInsightsName `
  --resource-type "Microsoft.Insights/components" `
  --properties '{\"Application_Type\":\"web\"}'

# create an app service plan
$planName = "plan-$suffix"
az appservice plan create -n $planName -g $resourceGroup `
    -l $location --sku B1

# create a web app
$webappName = "web-app-$suffix"
az webapp create -n $webappName -p $planName -g $resourceGroup

If we navigate to the Web App in the Azure Portal, and click on the Application Insights tab, we'll see that Application Insights has not been enabled yet.

image

That's because we haven't told our Web App which Application Insights instance to use. Let's do that next...

2 - Connect to Application Insights

To connect the Web App to our Application Insights instance, we need to set the APPINSIGHTS_INSTRUMENTATIONKEY Application Setting on the Web App:

# get the application insights key
$appInsightsKey = az resource show -g $resourceGroup -n $appInsightsName `
    --resource-type "Microsoft.Insights/components" `
    --query "properties.InstrumentationKey" -o tsv

# set the key on our web app
az webapp config appsettings set -n $webappName -g $resourceGroup `
  --settings "APPINSIGHTS_INSTRUMENTATIONKEY=$appInsightsKey"

Now if we visit the Web App in the Azure Portal and click on Application Insights again, we can see that we have associated this Web App with Application Insights, but it looks like we're not quite done yet:

image

Here we're being promoted to enable the Application Insights site extension, which we could do by clicking the button in the portal, but how can we automate this? That turned out to be quite a difficult task, as I found all kinds of conflicting information on how to to enable this extension via ARM templates.

However, it turns out that it's as simple as setting a few more Application Settings on our Web App, and you can read the documentation for those settings here.

3 - Turn on the Application Insights extension

To turn on the Application Insights Agent extension, we need to set the ApplicationInsightsAgent_EXTENSION_VERSION to a value of ~2:

az webapp config appsettings set -n $webappName -g $resourceGroup `
  --settings "ApplicationInsightsAgent_EXTENSION_VERSION=~2"

Now if we go again to the portal, we can see that we've got Application Insights set up correctly:

image

However, there are a number of configuration options that you can set in this UI, including what the default collection level is:

image

In our case, it's already set to "recommended", but if we wanted to explicitly configure that, we can set the XDT_MicrosoftApplicationInsights_Mode setting to recommended:

az webapp config appsettings set -n $webappName -g $resourceGroup `
    --settings "XDT_MicrosoftApplicationInsights_Mode=recommended"

4 - Tracing SQL commands

One unfortunate thing is that with the default settings the actual SQL commands that your application runs won't be visible in Application Insights.

image

Viewing the actual SQL can be invaluable when you're debugging a performance issue, but this behaviour is off by default as it has performance implications. However, if you do need to turn it on, then you simply need to set the InstrumentationEngine_EXTENSION_VERSION and XDT_MicrosoftApplicationInsights_BaseExtensions application settings to ~1:

az webapp config appsettings set -n $webappName -g $resourceGroup `
  --settings "InstrumentationEngine_EXTENSION_VERSION=~1" `
  "XDT_MicrosoftApplicationInsights_BaseExtensions=~1"

And now we we can see that SQL Command tracing is enabled for our site:

image

The ARM template way

Of course, you may prefer to use an ARM template, and so here's a snippet of the Web App resource from an ARM template that sets up all these settings automatically.

{
  "apiVersion": "2016-03-01",
  "name": "[variables('webSiteName')]",
  "type": "Microsoft.Web/sites",
  "location": "[parameters('location')]",
  "dependsOn": [
    "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
    "[resourceId('microsoft.insights/components/', variables('appInsightsName'))]"
  ],
  "tags": {
    "[concat('hidden-related:', resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName')))]": "empty",
    "displayName": "Website"
  },
  "properties": {
    "name": "[variables('webSiteName')]",
    "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
    "siteConfig": {
      "appSettings": [
        {
          "name":"APPINSIGHTS_INSTRUMENTATIONKEY",
          "value":"[reference(concat('microsoft.insights/components/', variables('appInsightsName'))).InstrumentationKey]"
        },
        {
          "name":"ApplicationInsightsAgent_EXTENSION_VERSION",
          "value":"~2"
        },
        {
          "name":"XDT_MicrosoftApplicationInsights_Mode",
          "value":"recommended"
        },
        {
          "name":"InstrumentationEngine_EXTENSION_VERSION",
          "value":"~1"
        },
        {
          "name":"XDT_MicrosoftApplicationInsights_BaseExtensions",
          "value":"~1"
        }
      ]
    }
  }
},
Want to learn more about the Azure CLI? Be sure to check out my Pluralsight course Azure CLI: Getting Started.

0 Comments Posted in:

Audio pipelines

In audio programming, you often need to rapidly process buffers of audio. When you record audio, several times a second a new buffer of audio is presented that is usually saved to a file or injected into an audio processing pipeline. And when you play audio, several times a second you are pulling audio out of an audio processing pipeline to provide new buffers to the soundcard.

There are some scenarios in which both happen at the same time. You are receiving audio, either by capturing it from a soundcard input, or by receiving it over the network (e.g. in an online voice chat), and placing that audio into an audio processing pipeline. But at the same time you are playing audio, reading it out of the pipeline.

This requires a high-performance pipeline to avoid glitches or dropouts in audio, and in NAudio, BufferedWaveProvider provides this functionality. This is backed by a circular memory buffer (to avoid memory allocations - an important consideration for high performance code), and is thread-safe, meaning that it can be safely read from and written to on different threads.

System.IO.Pipelines.Pipe

BufferedWaveProvider has worked well for many years, but I was very interested to see that the new System.IO.Pipelines.Pipe class solves a very similar problem. It's intended for situations where you need to efficiently parse or process data while it is being received, and is especially useful when one thread is writing to the pipe, and another thread is reading, and the reading thread might need pause and wait for more data to be written to the pipe before it can continue.

The new Pipe class lets you work with Span<T>, which I've written about before from an audio processing perspective - it offers a very efficient way of working with memory, helping you to reduce allocations and copies.

Let's see how to work with a Pipe.

Writing to a Pipe

Let's start by creating a new Pipe and get hold of it's PipeWriter which we can use for writing. The Pipe constructor does have some configuration options to fine-tune exactly how it works, but I'm just using the defaults here.

var pipe = new Pipe();
var writer = pipe.Writer;

Now, whenever we get some new audio date, we can write it into the pipe by calling WriteAsync. Here I'm just filling a byte array with random data, but normally you'd be writing the audio buffer received from the soundcard or over the network.

// just for demo purposes - get some random data to add to our pipe
var r = new Random();
var buffer = new byte[1024];
r.NextBytes(buffer);

// write it to the pipe
await writer.WriteAsync(buffer);

We can keep writing to the pipe without anything reading from it, but after a certain (configurable) threshold it will pause while trying to write.

Reading from a Pipe

Now, we can read from the pipe on another thread, and the nice thing about a pipe model is that read sizes do not have to match write sizes. This is very helpful in dealing with audio, as often you want to read a number of samples that is a power of 2, to simplify passing audio through algorithms like a Fast Fourier Transform (FFT), but the audio is not typically arriving in the correct buffer sizes.

Reading is simply a matter of calling ReadAsync which returns a ReadResult containing a Buffer property. The Buffer is not actually a Span<T> like you might expect. Instead it's a ReadOnlySequence<byte>, which is because the data returned might actually be backed by more than one non-contiguous block of memory. There are a variety of ways to access the data in the sequence - you can enumerate through it as a sequence of ReadOnlyMemory<T>, or you can slice it and copy it into a byte array or Span as I show below:

var res = await pipe.Reader.ReadAsync();
// to slice the returned buffer
var slice = res.Buffer.Slice(startPos, length);
// to copy the returned buffer into a byte array / span:
slice.CopyTo(buffer);

Just because you read from the pipe, doesn't mean the read position has advanced yet, so you need to explicitly call AdvanceTo to move forward in the pipe. This can be a bit confusing as you can specify two positions here - the "consumed" position, which indicates the end position of the data you've actually consumed and don't want to see again, and the "examined" position which is how far you have looked. This is relevant if you are parsing data, and the end of the buffer might contain an incomplete data structure that you want to wait for more data to be available before continuing to parse.

To indicate that we've consumed everything we read from the pipe, we can just do this:

pipe.Reader.AdvanceTo(res.Buffer.End);

Or in the case of audio, where I want to read in certain block sizes (BlockSize in my example), then I'd work through as many blocks as are available, and then tell the pipe that I've consumed up to the start of the first incomplete block, but I've examined everything.

var res = await pipe.Reader.ReadAsync();
var buffer = res.Buffer;
while (buffer.Length >= BlockSize)
{
    var block = buffer.Slice(0,BlockSize);
    // TODO process the first block of ReadSize bytes here
    // slice to get the next block
    buffer = buffer.Slice(BlockSize);
}

// advance the pipe to the start of the first incomplete block
pipe.Reader.AdvanceTo(buffer.Start, res.Buffer.End);

One thing to be aware of is that after you call AdvanceTo you will not get anything back from the pipe reader until there has been another write (even if you didn't "examine" all the way to the end of the buffer). That's why my example above has a loop, to process all blocks in the data returned by ReadAsync.

Limitations

Could Pipe be used instead of my existing circular buffer approach to BufferedWaveProvider in NAudio? Well, there are a few differences and limitations.

First, Pipe does not give you any indication of how many bytes are currently buffered. In NAudio, I often use that information to decide whether to pause audio playback, to avoid stuttering playback in internet radio streaming scenarios where there is a poor network connection.

Another feature I provide in BufferedWaveProvider is an option to discard incoming audio if the buffer is full. The Pipe class does not anticipate that you would want to do this (which makes sense for its intended use case of parsing received data). You'd probably need to track externally how many bytes were buffered to replicate this behaviour.

BufferedWaveProvider also has a "pad with silence" option, so you can always read your desired number of bytes, just with silence appended to fill in any missing data. This is good for live chat scenarios where network issues may mean there is no buffered audio available to play but we don't want to pause the playback device. Although Pipe doesn't offer such a feature, it wouldn't be too hard to replicate by adding the padding after calling ReadAsync.

Finally, Pipe has an async programming model - you await the ReadAsync and WriteAsync methods. In NAudio, although there are multiple threads involved, you tend to prefer to do all your audio processing on a single thread for performance reasons (and certain audio APIs require you to always call them from the same thread). I believe that Pipe will try to use the SynchronizationContext if it is available, but usually there isn't one on an audio playback thread, so code using Pipe would end up switching to different threadpool threads as you worked through the pipe.

Performance

I was interested to test how well Pipe performs compared to NAudio's BufferedWaveProvider.

It was a little tricky to come up with a fair benchmark, since the way BufferedWaveProvider works is that the call to Read is not blocking - you're expected to call it periodically when you need the next buffer of audio to play, and so if the required amount of audio isn't present, it usually pads with silence. Pipe on the other hand will block on a call to ReadAsync until more data has been written. So as a compromise, my benchmark was single-threaded with alternating writes and reads, but using different desired read block sizes to write block sizes. I then saw how quickly I could get about an hours worth of (random) audio through.

Here's the code for the BufferedWaveProvider benchmark:

private void ReadWriteBufferedWaveProvider()
{
    var r = new Random();
    var writeBuffer = new byte[WriteSize];
    var readBuffer = new byte[ReadSize];
    var bytesWritten = 0;
    var bytesRead = 0;
    while (bytesRead < TotalBytes)
    {
        // fill the buffer with random data
        r.NextBytes(writeBuffer);
        // write a block into the BufferedWaveProvider
        bufferedWaveProvider.AddSamples(writeBuffer, 0, writeBuffer.Length);
        bytesWritten += writeBuffer.Length;

        // read as many full blocks as we can
        while(bufferedWaveProvider.BufferedBytes > ReadSize)
        {
            var read = bufferedWaveProvider.Read(readBuffer, 0, ReadSize);
            bytesRead += read;
        }
    }
}

And for the Pipe benchmark:

private async Task ReadWritePipe()
{
    var r = new Random();
    var writeBuffer = new byte[WriteSize];
    var bytesWritten = 0;
    var bytesRead = 0;
    while (bytesRead < TotalBytes)
    {
        // fill the buffer with random data
        r.NextBytes(writeBuffer);
        // write it into the pipe
        await pipe.Writer.WriteAsync(writeBuffer);
        bytesWritten += writeBuffer.Length;

        // perform a single read from the pipe
        var res = await pipe.Reader.ReadAsync();
        
        // process as many read blocks as we can
        var buffer = res.Buffer;
        while (buffer.Length >= ReadSize)
        {
            // here's where we'd process a single block 
            // var currentBlock = buffer.Slice(0, ReadSize)
            buffer = buffer.Slice(ReadSize);
            bytesRead += ReadSize;
        }
        // tell the pipe we've "consumed" up to the start of the first incomplete block
        // and we've "examined" the whole thing
        pipe.Reader.AdvanceTo(buffer.Start, res.Buffer.End);
    }
}

I used Benchmark.NET to compare these approaches at different read and write block sizes, and the results were very close - with NAudio's BufferedWaveProvider slightly faster:

|               Method | TotalBytes | ReadSize | WriteSize |    Mean |    Error |   StdDev |     Gen 0 | Gen 1 | Gen 2 |  Allocated |
|--------------------- |----------- |--------- |---------- |--------:|---------:|---------:|----------:|------:|------:|-----------:|
|                 Pipe |  600000000 |     1000 |      1500 | 4.341 s | 0.0455 s | 0.0403 s | 2000.0000 |     - |     - | 9376.77 KB |
| BufferedWaveProvider |  600000000 |     1000 |      1500 | 4.163 s | 0.0501 s | 0.0469 s |         - |     - |     - |    2.77 KB |
|                 Pipe |  600000000 |     1000 |      6000 | 4.202 s | 0.0616 s | 0.0576 s | 1000.0000 |     - |     - | 4693.66 KB |
| BufferedWaveProvider |  600000000 |     1000 |      6000 | 4.181 s | 0.0593 s | 0.0555 s |         - |     - |     - |    7.16 KB |
|                 Pipe |  600000000 |     5000 |      1500 | 4.342 s | 0.0570 s | 0.0505 s | 1000.0000 |     - |     - | 7501.77 KB |
| BufferedWaveProvider |  600000000 |     5000 |      1500 | 4.323 s | 0.0993 s | 0.1326 s |         - |     - |     - |    6.67 KB |
|                 Pipe |  600000000 |     5000 |      6000 | 4.157 s | 0.0596 s | 0.0498 s |         - |     - |     - | 2818.66 KB |
| BufferedWaveProvider |  600000000 |     5000 |      6000 | 4.140 s | 0.0631 s | 0.0591 s |         - |     - |     - |   11.06 KB |

I had mixed feelings about these results. On the one hand, I like the fact that it validates that what I initially created some 15 years ago in NAudio actually performs pretty well. I was already following best practices of avoiding allocations, and minimising the use of locks, so there aren't too many obvious ways it could be optimised further. On the other hand, I was expecting that Pipe might be even faster. I suspect the main reason it isn't in this benchmark is simply because it has an async API, compared to the BufferedWaveProvider which doesn't use await at all. The Pipe benchmark also seems to allocate a fair bit more memory than I was expecting.

Summary

Pipe is another great addition to the high performance toolbox for .NET developers, and is particularly good for scenarios where you are consuming data at a different rate or in different block sizes to the rate at which you receive it. Although it serves a similar need to BufferedWaveProvider in NAudio, the specific requirements of audio pipelines are perhaps not quite the right fit for Pipe which was more designed with parsing HTTP requests in mind.

The other takeaway from this experiment, is that for high performance scenarios, taking care to write allocation free code (and minimise use of locking) will go a long way.

Want to get up to speed with the the fundamentals principles of digital audio and how to got about writing audio applications with NAudio? Be sure to check out my Pluralsight courses, Digital Audio Fundamentals, and Audio Programming with NAudio.

0 Comments Posted in:

Azure Blob Storage provides the concept of “shared access signatures”, which are a great way to grant time-limited access to read from (or write to) a specific blob in your container.

“SAS” vs “SAS Token” vs “SAS URI”?

The terminology is confusing, as “SAS” on its own can be used to refer to the entire “SAS URI” or sometimes the “SAS Token”, or even just the “signature”. Here's my understanding of what the terms are:

Here is an example of a SAS URI. This is a full URI that can be used to access a blob:

https://myaccount.blob.core.windows.net/sascontainer/sasblob.txt?sv=2015-04-05&st=2015-04-29T22%3A18%3A26Z&se=2015-04-30T02%3A23%3A26Z&sr=b&sp=rw&sip=168.1.5.60-168.1.5.70&spr=https&sig=Z%2FRHIX5Xcg0Mq2rqI3OlWTjEg2tYkboXr1P9ZUXDtkk%3D

Here I have highlighted just the SAS Token portion of the SAS URI. This is the query string appended to the blob’s URI:

https://myaccount.blob.core.windows.net/sascontainer/sasblob.txt?sv=2015-04-05&st=2015-04-29T22%3A18%3A26Z&se=2015-04-30T02%3A23%3A26Z&sr=b&sp=rw&sip=168.1.5.60-168.1.5.70&spr=https&sig=Z%2FRHIX5Xcg0Mq2rqI3OlWTjEg2tYkboXr1P9ZUXDtkk%3D

Here I have highlighted just the signature only. This is calculated from the rest of the SAS URI, and requires the Storage Account connection string to calculate. Note that creating this signature is purely in-memory operation – as long as you have the Storage Account connection string, you can generate one without the target blob needing to exist, or needing access to the Storage Account REST API.

https://myaccount.blob.core.windows.net/sascontainer/sasblob.txt?sv=2015-04-05&st=2015-04-29T22%3A18%3A26Z&se=2015-04-30T02%3A23%3A26Z&sr=b&sp=rw&sip=168.1.5.60-168.1.5.70&spr=https&sig=Z%2FRHIX5Xcg0Mq2rqI3OlWTjEg2tYkboXr1P9ZUXDtkk%3D

SAS Usage Guidelines

Shared access signatures can be a great way to share files between microservices in a larger application, as well as making files available to end users for download or display in a webpage. But they can cause problems if used incorrectly. So here are a few guidelines I shared with my development team recently, that might be relevant for your projects too.

  1. Prefer to pass around full SAS URIs. Sometimes I see code that passes just the SAS token, and the name of the file, but this means that the consuming code has to make several assumptions to regenerate the full SAS URI, including the name of the Storage Account. This can also cause problems if you want to use the Azure Storage Emulator whose URI structure is different. By providing a full SAS URI, the receiving code can be completely agnostic about where the file is hosted. For example, if it points at an Amazon S3 Bucket instead, your consuming code won't need to change at all.

  2. Keep the lifetime short, (but not too short). Obviously, from a security perspective, you want to keep SAS lifetimes as short as possible. It does need to be at least long enough to allow the recipient to download the item. But if you put a SAS URI in a queue message, then the lifetime of the SAS should not be less than the TTL (time to live) of the queue message. Otherwise, if the message sits in a queue for a couple of days before being read, it will be useless by the time it is consumed.

  3. Avoid long-lived SAS tokens. It can be tempting to create very long-lived SAS tokens if you want to share an item long-term, but this is generally a bad idea. First, the obvious issue is that there is a greater window of time when it could be accessed if it falls into the wrong hands. But second, SAS tokens become invalidated whenever you cycle your Storage Account keys, so the recipient of the SAS token will need a way to refresh it anyway if they really do need long-term access to the file. Which brings us to...

  4. Generate SAS tokens on-demand wherever possible. Generally, rather than creating a long-lived SAS token and storing it in a database, it's better to have an on-demand process for generating a SAS token at the point the blob access is needed. This allows you to keep the durations short.

  5. Don't bother retrying access denied errors. If you're writing code that attempts to use a SAS URI, and you get access denied, consider it a fatal error - it more than likely means it has expired. (BTW, when I create a SAS token I usually set the start time to five minutes ago, to minimize the chance that system clock inaccuracy results in a SAS token that isn't valid yet)

  6. Avoid issuing container-level SAS tokens. Obviously this one depends on what else might be in the container, but generally it's better to use the principle of "least privilege", and generate a SAS token only for the specific blobs that are required. I'd like to see a SAS feature in the future where you could grant access with a wildcard to all blobs matching a prefix, but I don't think that is currently possible. (If you do need to create a container level SAS token, you I created a tutorial here)

  7. Never write SAS tokens into log messages. Log messages are seen by support staff, who should not have access to private customer data. I like to strip off the SAS token portion of the URI before writing it to the logs, allowing support to see which file had a problem, but not the contents of the file.