Monitor Windows EC2 Instance Free Drivespace via Cloudwatch

Amazon offers a lot of great CloudWatch metrics out of the box for EC2 instances, but there is a key metric that is missing.  Remaining available drive space.

Luckily, there is a relatively easy way to resolve this.  AWS offers us the ability to add in our own metrics that allow us to monitor pretty much anything we want.  In this case, we are going to create something to report back to CloudWatch about the free drivespace on our EC2 instance.

In preparation for this exercise, we are going to do a little prep work.

To start, ensure that the AWS SDK for .Net is available on your EC2 instance.  It should exist in C:\Program Files (x86)\AWS SDK for .NET\ by default.  If it doesn’t for whatever reason, download and install it.

Next, collect your Access Key and the Access Key Secret and put them to the side.  They’ll be used shortly.

The last thing you will need to gather is the instance ID for the EC2 instance that we’re going to collect metrics for.  If you are uncertain what this is, there are a few different ways to find it.  If you are running a more recent image, it is in the top right corner on the background image.  The best way to get the instance ID however is to log in to the AWS Management Console, go to the EC2 section and the instance ID will be shown in the grid.  It should start with a lowercase “i” followed by a dash and a hex string. e.g. i-a1b2c3d4

The next step is putting together a PowerShell script that will utilize all of the things that we have collected to this point to gather and report on the metrics that we’re after.

So, let’s get started.

Open your preferred PowerShell script editor and create a new ps1 file.  It doesn’t matter what it is called. I named mine **updatedrivespacemetrics.ps1 **for clarity.

At the top of the script, we are going to import the AWS assembly and set the access key values.

#Import the AWS SDK assembly  
Add-Type -Path "C:\Program Files (x86)\AWS SDK for .NET\bin\AWSSDK.dll" 

$secretKeyID="ABCDEF1234567890ABCD" #Get Instance ID $instanceId="i-a1b2c3d4"

Next, create a request object to store the new Metrics and give it a name that you will be able to easily recognize later on.

#Create request  
$request = New-Object -TypeName Amazon.CloudWatch.Model.PutMetricDataRequest   
$request.NameSpace = "CUSTOM-Freespace"

Time to collect the values.  We are going to use WMI to get the freespace from the drives on our instance.

#Get Free Space 
$freeSpace=Get-WmiObject -Class Win32_LogicalDisk | 
               Select-Object -Property DeviceID, @{Name='FreeSpaceGB';Expression={$_.FreeSpace/1GB}} | 
               Where-Object {$_.DeviceID -eq "C:" -or $_.DeviceID -eq "D:" }

Let’s look at what this statement is doing.

Get-WmiObject -Class Win32_LogicalDisk

This will return an array of all of the drives on the machine with their DeviceID, DriveType, Freespace, Size and VolumeName.

From this array, we are going to select the *DeviceID *and the FreeSpace.  However, *FreeSpace *is returning bytes and we need something that is a little easier to parse, so we are going to turn it into gigabytes.  To do this we are going to use an expression to modify the property value before we pull it back.


Last, we are going to limit the drives that we pull back.  This part is optional, but can be useful if there are only specific drives that you are interested in.  In this case, I have narrowed it down to the C and D drives.

Where-Object {$_.DeviceID -eq "C:" -or $_.DeviceID -eq "D:" }

Time to start collecting this information into a way that AWS can parse it.  The first step to do this is creating some basic dimensions that will differentiate our custom metric.  To keep things simple we are going to create two dimensions, Role and InstanceID.

#Create dimensions 
$dimensions = New-Object System.Collections.ArrayList 
$dimension1 = New-Object -TypeName Amazon.CloudWatch.Model.Dimension 
$dimension2 = New-Object -TypeName Amazon.CloudWatch.Model.Dimension 

$dimension1.Name = "Role" 
$dimension1.Value = "Test Server" 
$dimension2.Name = "InstanceID" 
$dimension2.Value = $instanceId 


With the dimensions in hand we are going to put everything together into a metric.  For each drive that we collected information on earlier, we are going to create a *MetricDatum *and populate it with the appropriate values.

#Create metrics 
$metrics = New-Object System.Collections.ArrayList 
Foreach ($item in $freeSpace) { 
     $metric = New-Object -TypeName Amazon.CloudWatch.Model.MetricDatum     
     $metric.MetricName = $item.DeviceID + "free space" 
     $metric.Value = $item.FreeSpaceGB $metric.Unit = "Gigabytes"   
     $metric=$metric.WithDimensions($dimensions) $metrics.Add($metric) 

After the metrics are created, we need to update the request object that we created earlier with the metrics data

$request = $request.WithMetricData($metrics)

Finally, we are going to submit all of this information back to AWS.

#Perform the request 
$client= Amazon.AWSClientFactory]::CreateAmazonCloudWatchClient($secretKeyID,$secretAccessKeyID) 

That’s it!  Now we have a script that will push information on available drivespace up to CloudWatch.  The first time it is run it will create the metric if it doesn’t exist.  Each subsequent time, it will just add a new data point to the existing metric.

The final step is scheduling this so that it runs consistently.  The easiest way to accomplish this is the built in Windows Task Scheduler.  I won’t go through the steps for that here, but I would suggest setting it up to run every 5 minutes.  If you are uncertain how to call your script, powershell c:\jobs\updatedrivespacemetrics.ps1 will get the job done (replace c:\jobs\ with the location of your script).

Now that you have everything set up, you can go into the AWS management console, go to the CloudWatch page and you should see your new metric.

Note:  You will need to set this up for each instance that you want to gather drivespace on.  Each instance will need its own copy of the script and have the task scheduler set up appropriately.  As you copy the script from instance to instance, you will need to change the InstanceID in the script to reflect the appropriate instance.