|
import boto3, argparse, json |
|
from pprint import pprint |
|
|
|
def tag_name(resource): |
|
try: |
|
for t in resource.tags: |
|
if t['Key'] == 'Name': |
|
return t['Value'] |
|
return 'null' |
|
except TypeError: |
|
return 'null' |
|
def default(name): |
|
if name == 'dashboard_name': |
|
from string import ascii_uppercase, digits |
|
from random import choice as random_choice |
|
return 'Dashboard_{}'.format(''.join(random_choice(ascii_uppercase + digits) for _ in range(5))) |
|
elif name == 'volume_group': |
|
return 'default' |
|
elif name == 'aws_profile': |
|
return 'default' |
|
elif name == 'dump': |
|
return False |
|
class MyList(list): |
|
def append(self,arg): |
|
if isinstance(arg,list): |
|
self.extend(arg) |
|
else: |
|
super(MyList, self).append(arg) |
|
|
|
parser = argparse.ArgumentParser(description='Generate a CloudWatch Dashboard for an EC2 Instance') |
|
parser.add_argument('instance_id', action='store', help='ID of the Instance') |
|
parser.add_argument('--dashboard_name', action='store', help='Name of the Dashboard', default=default('dashboard_name')) |
|
parser.add_argument('--skip_devices', action='append', help='Device Name to skip from the Dashboard', nargs='*', default=MyList([])) |
|
parser.add_argument('--volume_group', action='store', help='Name to give to the group of volumes', default=default('volume_group')) |
|
parser.add_argument('--aws_profile', action='store', help='AWS Profile to use. Defaults to "oren-nonprod"', default=default('aws_profile')) |
|
parser.add_argument('--dump', action='store_true', help='Dump JSON content instead of creating Dashboard', default=default('dump')) |
|
args = parser.parse_args() |
|
|
|
sess = boto3.Session(profile_name=args.aws_profile) |
|
ec2 = sess.resource('ec2') |
|
region = sess.region_name |
|
console_url = 'https://{}.console.aws.amazon.com/ec2/v2/home?region={}'.format(region, region) |
|
|
|
instance = ec2.Instance(args.instance_id) |
|
volumes = [ ec2.Volume(v['Ebs']['VolumeId']) for v in instance.block_device_mappings if not v['DeviceName'].replace('/dev/', '') in args.skip_devices ] |
|
|
|
widgets = [ ] |
|
# EC2 Widgets |
|
instance_metrics = [ |
|
{ 'name': 'CPUUtilization', 'x': 0, 'y': 0, }, |
|
{ 'name': 'NetworkIn', 'x': 6, 'y': 0, }, |
|
{ 'name': 'NetworkOut', 'x': 12, 'y': 0, }, |
|
{ 'name': 'DiskReadBytes', 'x': 18, 'y': 0, }, |
|
] |
|
for m in instance_metrics: |
|
widgets.append( |
|
{ |
|
'x': m['x'], 'y': m['y'], |
|
'type': 'metric', 'width': 6, 'height': 6, |
|
'properties': { |
|
'metrics': [ [ 'AWS/EC2', m['name'], 'InstanceId', args.instance_id ] ], |
|
'region': region, |
|
'title': 'EC2 {}'.format(m['name']), |
|
'view': 'timeSeries', 'stacked': False, 'period': 300, |
|
} |
|
} |
|
) |
|
# EBS Widgets |
|
volume_metrics = [ |
|
{ 'name': 'VolumeReadOps', 'x': 0, 'y': 6, }, |
|
{ 'name': 'VolumeReadBytes', 'x': 6, 'y': 6, }, |
|
{ 'name': 'VolumeTotalReadTime', 'x': 12, 'y': 6, }, |
|
{ 'name': 'VolumeQueueLength', 'x': 18, 'y': 6, }, |
|
{ 'name': 'VolumeReadBytes', 'x': 12, 'y': 12, }, |
|
] |
|
for m in volume_metrics: |
|
metric = { |
|
'x': m['x'], 'y': m['y'], |
|
'type': 'metric', 'width': 6, 'height': 6, |
|
'properties': { |
|
'metrics': [ [ 'AWS/EBS', m['name'], 'VolumeId', volumes[0].id ], ], |
|
'region': region, 'title': '{} {}'.format(args.volume_group, m['name']), |
|
'view': 'timeSeries', 'stacked': False, 'period': 300, |
|
} |
|
} |
|
for v in volumes[1:]: |
|
metric['properties']['metrics'].append([ '...', v.id ]) |
|
|
|
widgets.append(metric) |
|
# Markdown Widget |
|
markdown = [ '# EC2 Instance', '' |
|
'Instance name: {} ID: [{}]({}#Instances:search={})'.format(tag_name(instance), instance.id, console_url, instance.id), |
|
'', '## Volumes', 'List of volumes detailed here and links to their Console pages below.', '', |
|
'### {}'.format(args.volume_group), '', |
|
'Device | Volume Name | Volume ID', '----|---------|---------', |
|
] |
|
for v in volumes: |
|
markdown.append ( '{} | {} | [{}]({}#Volumes:search={};sort=size)'.format ( v.attachments[0]['Device'].replace('/dev/', ''), |
|
tag_name(v), v.id, console_url, v.id |
|
) |
|
) |
|
widgets.append({ 'type': 'text', 'x': 0, 'y': 12, 'width': 12, 'height': 9, 'properties': { 'markdown': "\n".join(markdown) } } ) |
|
|
|
dash_body = json.dumps({ 'widgets': widgets }) |
|
if args.dump: |
|
print(dash_body) |
|
else: |
|
cw = sess.client('cloudwatch') |
|
resp = cw.put_dashboard(DashboardName=args.dashboard_name, DashboardBody=dash_body) |
|
print('boto3 Response:\n{}'.format(json.dumps(resp, indent=2))) |
|
print('Link to the Dashboard:\nhttps://{}.console.aws.amazon.com/cloudwatch/home?region={}#dashboards:name={}'.format(region, region, args.dashboard_name)) |