Skip to content

Instantly share code, notes, and snippets.

@greglu
Created July 3, 2015 07:06
Show Gist options
  • Select an option

  • Save greglu/41caeb6ab9489b93d93b to your computer and use it in GitHub Desktop.

Select an option

Save greglu/41caeb6ab9489b93d93b to your computer and use it in GitHub Desktop.
Checks reserved instance utilization of an EC2 account
#!/bin/bash
# Checks active, non-spot, EC2 instances against the list reserved instances
# in order to determine which availability-zone/instance-type combinations
# are paying for on-demand prices (that could benefit from reserved instance
# purchase), or current reservations that aren't being used.
#
# Dependencies:
# * awscli (http://aws.amazon.com/cli/)
# * jq (http://stedolan.github.io/jq/)
# * ruby
#
# This script also assumes that the awscli tool is associated to an account.
#
# The MIT License (MIT)
#
# Copyright (c) 2015 Gregory Lu
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
set -e
for i in aws jq ruby; do
if ! which $i &>/dev/null; then
echo "Error: this script requires $i"
exit 1
fi
done
ACTIVE_INSTANCES_FILE=$(mktemp -t active-instances)
RESERVED_INSTANCES_FILE=$(mktemp -t reserved-instances)
echo -n "Retrieving active instance listings... "
aws ec2 describe-instances > $ACTIVE_INSTANCES_FILE
echo "done"
echo -n "Retrieving reserved instance listings... "
aws ec2 describe-reserved-instances --filters Name=state,Values=active > $RESERVED_INSTANCES_FILE
echo "done"
RESERVED_PARSED=$(mktemp -t reserved-parsed)
ACTIVE_PARSED=$(mktemp -t active-parsed)
cat $RESERVED_INSTANCES_FILE | jq '.ReservedInstances | map({ type: (.AvailabilityZone + "/" + .InstanceType), count: .InstanceCount }) | group_by(.type) | map({key: .[0].type, value: (reduce .[] as $item (0; . + $item.count)) }) | from_entries' > $RESERVED_PARSED
cat $ACTIVE_INSTANCES_FILE | jq '.Reservations | map(.Instances[0] | select(.InstanceLifecycle != "spot") | {type: ((.Placement .AvailabilityZone) + "/" + .InstanceType)}) | group_by(.type) | map({key: .[0].type, value: length}) | from_entries' > $ACTIVE_PARSED
COMBINED=$(ruby -e "require 'json'; active = JSON.load(File.read('${ACTIVE_PARSED}')); reserved = JSON.load(File.read('${RESERVED_PARSED}')); puts JSON.dump(active.merge(reserved) { |_, x, y| x - y })")
echo ""
echo "-------------------------------------------------------------------------------"
echo "Current counts of active, non-spot, instances without an associated reservation"
echo "(paying on-demand pricing for these instances)"
echo "-------------------------------------------------------------------------------"
echo $COMBINED | jq 'to_entries | map(select(.value > 0)) | from_entries'
echo ""
echo "--------------------------------------------------------"
echo "Current reservations without active instances using them"
echo "(already paid for these reserved instances)"
echo "--------------------------------------------------------"
echo $COMBINED | jq 'to_entries | map(select(.value < 0)) | from_entries'
echo ""
exit 0
@philfreo
Copy link
Copy Markdown

This has a math/logic bug when you have a Reserved Instance with no corresponding Active Instances because the ruby merge/subtraction block { |_, x, y| x - y } never gets called when there isn't a conflict between the two dicts.

There's a one-line fix if you're okay with looking at negative values in the second printed section:

Change:

puts JSON.dump(active.merge(reserved) { |_, x, y| x - y })

To:

puts JSON.dump(active.merge(reserved.map { |x,y| [x,y*-1] }.to_h) { |_, x, y| x + y })

Finally, it should also be noted that this doesn't check for matching Platform (e.g. VPC vs. non-VPC).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment