Monday, January 7, 2019

Log Azure DevOps Build Times to Application Insights

I’ve been working a lot with Azure DevOps recently to automate Dynamics 365 CE deployments. One of the first things I noticed was that using the hosted build agents resulted in slower builds than I was used to, comparatively speaking anyway to performing the same operations on your local machine. It’s not surprising given that for each build the agent is spun up and torn down and nothing can be re-used from previous runs. Whereas on your local machine NuGet packages are already downloaded, any build tools are already installed, etc. so naturally it’s way faster. Building a simple plug-in and running a few unit tests was taking close to 2 minutes. Which is fine I guess considering rarely would anyone be staring at it waiting for it to finish. The thought though was that once you started building real projects with lots of unit tests the build could end up taking quite a long time to finish, potentially backing up other processes. I figured it would be a good idea to keep an eye on how long they’re taking by showing the average time for a build on a dashboard in hopes that it would draw attention if the times started getting long without anyone really noticing. I checked the Visual Studio Marketplace but didn’t find any existing tools to handle this so I thought I’d put one together using Application Insights to store the data. There was already a free dashboard widget available which could surface that data so I figured it would be the easiest route to take.

I wanted to log the time at the end of the build (knowing the results will be off a few seconds) as opposed to relying on some other process like a Web Hook / Azure Function combination. I decided to use a PowerShell task as the last step of the build to do the logging.

Have a look.

Here’s a quick rundown on what the code is doing.

Initial portion if calculating the number of minutes based on when the build started and when the code ran. I’m using one of the predefined variables called System.PipelineStartTime to establish the start. I’m not actually sure that’s documented anyplace as far as I can tell but if you were to do a dump of all the system variables it shows up. The number of minutes you end up with is relatively close to what is reported if you looked at the completed build in the UI, give or take 6-10 seconds. I figured that was an acceptable margin for error since you’d probably never look to optimize a build if you were only going to gain a few seconds.

You’ll need to provide the Instrumentation Key for your Application Insights instance so there’s a basic check to make sure that was provided. Of course store that in a Variable Group or at a Build Variable instead of hard coding to make changing it easy. There’s also the side benefit of hoping that nobody trolls my GitHub account because I had example code with hard coded credentials to simplify things for those people who are still learning. :)

I chose to use the Metric type in Application Insights to record the data being that it’s numeric in nature. You can see the JSON body of the request that is going to be made, most of it is the boilerplate request. There are two places where you can make changes to the data being sent. Under ‘metrics’ the ‘name’ can be changed to anything you’d like. I used “Build Time” to describe the type of metric being recorded and the the build definition name so I knew which build it came from. You could make it anything but be aware that this is used for aggregation so you won’t want any dynamic values here. The other place is under ‘properties’. Those can be any key/value pairs you want, so if you want to capture detail about the build agent, values from the predefined build variables, or something else entirely you can put it here and you’ll be able to query on it in Application Insights.

Finally is gives a check to see if the request was successful. It certainly doesn’t hurt to be verbose with the logging. Trying to debug failed builds when you have to wait 2+ minutes between runs is painful so do yourself a favor and do whatever you can to speed that process up.

Another quick note, I’m using 2 different formats when referencing variables and that is on purpose. The majority are in the format like $(Build.DefinitionName) but the instance where where the pipeline start time is used it’s in the format like $Env:System_PipelineStartTime. The reason being is that I have this script in a Task Group (which basically combines multiple steps into 1 that can be reused) and by default it will scan through all the variables you’re using and turn them into parameters. In this case I want the value to be retrieved at run time instead. I could have jumped though a few hoops and exported and reimported the task group after editing by hand but I figured out that just using $Env: instead causes it to be skipped when parameters are gathered.

In a future post I’ll walk through getting the information on an Azure DevOps dashboard.