Skip to content

Instantly share code, notes, and snippets.

@ftfarias
Last active August 29, 2018 14:11
Show Gist options
  • Save ftfarias/c5419f47c38329a90f924391fb7ece21 to your computer and use it in GitHub Desktop.
Save ftfarias/c5419f47c38329a90f924391fb7ece21 to your computer and use it in GitHub Desktop.
Script to list reserved instances in AWS
#! /usr/bin/env python
from argparse import ArgumentParser
import boto3
import pandas as pd
CSV_FILENAME = 'ec2_reserves.csv'
ec2 = boto3.client('ec2')
def get_instance_size(name):
size_name = name.split('.')[1]
if size_name == 'nano':
return 1 / 16
if size_name == 'micro':
return 1 / 8
if size_name == 'small':
return 1 / 4
if size_name == 'medium':
return 1 / 2
if size_name == 'large':
return 1
xlarge_size = 1 if size_name == 'xlarge' else int(size_name.replace('xlarge', ''))
return 2 * xlarge_size
def get_instance_family(name):
return name.split('.')[0]
def count_dedicated_reserves(df: pd.DataFrame) -> pd.Series:
group_columns = ['InstanceType', 'AvailabilityZone']
if 'InstanceCount' in df.columns:
sr_count = df.groupby(group_columns)['InstanceCount'].sum()
else:
sr_count = df.groupby(group_columns).size()
sr_count.index = sr_count.index.tolist()
return sr_count
def count_default_reserves(df: pd.DataFrame) -> pd.Series:
group_columns = ['InstanceFamily']
if 'InstanceLargeCount' in df.columns:
sr = df.groupby(group_columns)['InstanceLargeCount'].sum()
else:
sr = df.groupby(group_columns)['InstanceTypeSize'].sum()
sr.index = sr.index.values + '.large'
return sr
def concat_default_dedicated(sr_default: pd.Series, sr_dedicated: pd.Series) -> pd.Series:
return pd.concat(
[sr_default, sr_dedicated],
keys=['default', 'dedicated'],
names=['Tenancy', 'InstanceFamily']
)
def get_active_reserves() -> pd.Series:
response = ec2.describe_reserved_instances()
df = pd.DataFrame(response['ReservedInstances'])
df['InstanceFamily'] = df['InstanceType'].map(get_instance_family)
df['InstanceTypeSize'] = df['InstanceType'].map(get_instance_size)
df['InstanceLargeCount'] = df['InstanceTypeSize'] * df['InstanceCount']
loc_active = df['State'] == 'active'
loc_dedicated_tenancy = df['InstanceTenancy'] == 'dedicated'
loc_default_tenancy = df['InstanceTenancy'] == 'default'
sr_dedicated = count_dedicated_reserves(df.loc[loc_active & loc_dedicated_tenancy])
sr_default = count_default_reserves(df.loc[loc_active & loc_default_tenancy])
return concat_default_dedicated(sr_default, sr_dedicated)
def get_running_instances() -> pd.Series:
response = ec2.describe_instances()
df = pd.DataFrame([i for r in response['Reservations'] for i in r['Instances']])
df['AvailabilityZone'] = df['Placement'].map(lambda x: x['AvailabilityZone'])
df['Tenancy'] = df['Placement'].map(lambda x: x['Tenancy'])
df['InstanceFamily'] = df['InstanceType'].map(get_instance_family)
df['InstanceTypeSize'] = df['InstanceType'].map(get_instance_size)
loc_running = df['State'].map(lambda x: x.get('Name') == 'running')
loc_dedicated_tenancy = df['Tenancy'] == 'dedicated'
loc_default_tenancy = df['Tenancy'] == 'default'
sr_dedicated = count_dedicated_reserves(df.loc[loc_running & loc_dedicated_tenancy])
sr_default = count_default_reserves(df.loc[loc_running & loc_default_tenancy])
return concat_default_dedicated(sr_default, sr_dedicated)
def get_usage(sr_active_reserves: pd.Series, sr_active_instances: pd.Series) -> pd.DataFrame:
df_usage = pd.merge(
sr_active_reserves.to_frame('num_reserved'),
sr_active_instances.to_frame('num_running'),
how='outer',
right_index=True,
left_index=True
)
df_usage = df_usage.fillna(0)
df_usage['num_available'] = df_usage['num_reserved'] - df_usage['num_running']
return df_usage
def main(to_csv: bool):
sr_active_reserves = get_active_reserves()
sr_running_instances = get_running_instances()
df = get_usage(sr_active_reserves, sr_running_instances)
if to_csv:
print('Written to ' + CSV_FILENAME)
else:
df['running/reserved'] = df[['num_running', 'num_reserved']].apply(
lambda x: '{:g}/{:g}'.format(*x), axis=1)
df['num_available'] = df['num_available'].map('{:g}'.format)
print(df[['running/reserved', 'num_available']])
if __name__ == '__main__':
parser = ArgumentParser(description='Prints EC2 reserved instances and usage')
parser.add_argument('--to-csv', action='store_true',
help='Writes output to csv named ' + CSV_FILENAME)
args = parser.parse_args()
main(args.to_csv)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment