1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
| # find_waste.py
import boto3
from datetime import datetime, timedelta
ec2 = boto3.client('ec2')
cloudwatch = boto3.client('cloudwatch')
def find_idle_instances():
"""Find EC2 instances with <5% CPU over 7 days."""
instances = ec2.describe_instances(
Filters=[{'Name': 'instance-state-name', 'Values': ['running']}]
)
idle = []
for reservation in instances['Reservations']:
for instance in reservation['Instances']:
instance_id = instance['InstanceId']
# Get average CPU over 7 days
response = cloudwatch.get_metric_statistics(
Namespace='AWS/EC2',
MetricName='CPUUtilization',
Dimensions=[{'Name': 'InstanceId', 'Value': instance_id}],
StartTime=datetime.utcnow() - timedelta(days=7),
EndTime=datetime.utcnow(),
Period=86400, # 1 day
Statistics=['Average']
)
if response['Datapoints']:
avg_cpu = sum(d['Average'] for d in response['Datapoints']) / len(response['Datapoints'])
if avg_cpu < 5:
idle.append({
'instance_id': instance_id,
'type': instance['InstanceType'],
'avg_cpu': round(avg_cpu, 2),
'name': next((t['Value'] for t in instance.get('Tags', [])
if t['Key'] == 'Name'), 'unnamed')
})
return idle
def find_unattached_volumes():
"""Find EBS volumes not attached to any instance."""
volumes = ec2.describe_volumes(
Filters=[{'Name': 'status', 'Values': ['available']}]
)
return [{
'volume_id': v['VolumeId'],
'size_gb': v['Size'],
'monthly_cost': v['Size'] * 0.10 # gp2 pricing estimate
} for v in volumes['Volumes']]
def find_old_snapshots():
"""Find snapshots older than 90 days."""
snapshots = ec2.describe_snapshots(OwnerIds=['self'])
cutoff = datetime.utcnow() - timedelta(days=90)
old = []
for snap in snapshots['Snapshots']:
if snap['StartTime'].replace(tzinfo=None) < cutoff:
old.append({
'snapshot_id': snap['SnapshotId'],
'size_gb': snap['VolumeSize'],
'age_days': (datetime.utcnow() - snap['StartTime'].replace(tzinfo=None)).days
})
return old
# Run analysis
print("=== Idle Instances ===")
for i in find_idle_instances():
print(f" {i['instance_id']} ({i['type']}): {i['avg_cpu']}% avg CPU - {i['name']}")
print("\n=== Unattached Volumes ===")
for v in find_unattached_volumes():
print(f" {v['volume_id']}: {v['size_gb']}GB = ${v['monthly_cost']:.2f}/mo")
print("\n=== Old Snapshots ===")
for s in find_old_snapshots():
print(f" {s['snapshot_id']}: {s['size_gb']}GB, {s['age_days']} days old")
|