🧩 GOAL
✅ Find all EC2 instance IDs✅ Fetch average CPU utilization for each✅ Cover **October and November (2 months)**✅ Output a clear list or table (instance ID + average CPU%)
🧠 Step-by-Step Solution
1️⃣ Get All EC2 Instance IDs
aws ec2 describe-instances --region ap-south-1 --query "Reservations[].Instances[].InstanceId" --output text > instance_ids.txtThis will create a file instance_ids.txt with all instance IDs:
i-0123456789abcdef1i-0a1234abcd5678i-0ffedcba98765432...2️⃣ Loop Through Each Instance and Get CPU Average (for last 60 days)
#!/bin/bashREGION="ap-south-1"START_DATE=$(date -u -d '60 days ago' +%Y-%m-%dT%H:%M:%SZ)END_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)echo "InstanceID,AverageCPU(%)" > ec2_cpu_report.csvfor instance in $(aws ec2 describe-instances --region $REGION --query "Reservations[].Instances[].InstanceId" --output text); do avg_cpu=$(aws cloudwatch get-metric-statistics --metric-name CPUUtilization --start-time $START_DATE --end-time $END_DATE --period 86400 --namespace AWS/EC2 --statistics Average --dimensions Name=InstanceId,Value=$instance --region $REGION --query "Datapoints[].Average" --output text | awk '{ total += $1; count++ } END { if (count > 0) print total/count; else print 0 }') echo "$instance,${avg_cpu:-0}" >> ec2_cpu_report.csv echo "✔ Checked: $instance → Avg CPU = ${avg_cpu:-0}%"doneecho "✅ Report generated: ec2_cpu_report.csv"Save this as ec2_cpu_usage_report.sh, make it executable:
chmod +x ec2_cpu_usage_report.sh./ec2_cpu_usage_report.sh3️⃣ Output Example (ec2_cpu_report.csv):
Instance ID
Average CPU (%)
i-0123456789abcdef1
1.23
i-0a1234abcd5678
7.84
i-0ffedcba98765432
0.45
4️⃣ Interpretation:
-
Instances with <5% average CPU → likely idle / underutilized
-
Instances with >60–70% average CPU → potentially overloaded
-
You can use these insights for:
- Stopping idle ones
- Resizing (t3.xlarge → t3.medium, etc.)
- Applying Savings Plans
5️⃣ For October Only (Custom Date Range Example)
If you want October only, change:
START_DATE="2024-10-01T00:00:00Z"END_DATE="2024-10-31T23:59:59Z"And rerun the same script.
⚙️ Optional — More Metrics
You can easily expand this script to check:
- NetworkIn / NetworkOut
- DiskReadOps / DiskWriteOps
- EBSIdleTime
For example:
--metric-name NetworkOut--namespace AWS/EC2💡 Best Practice:
Once you generate ec2_cpu_report.csv, sort it:
sort -t, -k2 -n ec2_cpu_report.csv→ Gives you idle instances at the top (lowest CPU %).
Would you like me to modify this script so it:
- Checks both CPU and Network usage (to detect truly idle EC2s), and
- Generates a combined idle-instance summary (ready for cleanup)?
#!/bin/bash# ---------------------------------------------------------------# EC2 Utilization Analyzer - Cost Optimization Helper# Finds Idle and Overutilized EC2 Instances (CPU + Network)# Region: ap-south-1# ---------------------------------------------------------------REGION="ap-south-1"START_DATE=$(date -u -d '60 days ago' +%Y-%m-%dT%H:%M:%SZ)END_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)OUTPUT_FILE="ec2_utilization_report.csv"echo "InstanceID,Name,InstanceType,CPU_Avg(%),NetworkIn_Bytes,NetworkOut_Bytes,Status" > $OUTPUT_FILEecho "🔍 Checking EC2 instances in region: $REGION ..."INSTANCE_IDS=$(aws ec2 describe-instances --region $REGION --query "Reservations[].Instances[].InstanceId" --output text)for INSTANCE_ID in $INSTANCE_IDS; do # Get instance details read NAME TYPE <<< $(aws ec2 describe-instances --region $REGION --instance-ids $INSTANCE_ID --query "Reservations[].Instances[].{Name:Tags[?Key=='Name']|[0].Value,Type:InstanceType}" --output text) # --- Average CPU utilization --- CPU_AVG=$(aws cloudwatch get-metric-statistics --metric-name CPUUtilization --start-time $START_DATE --end-time $END_DATE --period 86400 --namespace AWS/EC2 --statistics Average --dimensions Name=InstanceId,Value=$INSTANCE_ID --region $REGION --query "Datapoints[].Average" --output text | awk '{ total += $1; count++ } END { if (count > 0) print total/count; else print 0 }') # --- Average Network In --- NET_IN=$(aws cloudwatch get-metric-statistics --metric-name NetworkIn --start-time $START_DATE --end-time $END_DATE --period 86400 --namespace AWS/EC2 --statistics Sum --dimensions Name=InstanceId,Value=$INSTANCE_ID --region $REGION --query "Datapoints[].Sum" --output text | awk '{ total += $1; count++ } END { if (count > 0) print total/count; else print 0 }') # --- Average Network Out --- NET_OUT=$(aws cloudwatch get-metric-statistics --metric-name NetworkOut --start-time $START_DATE --end-time $END_DATE --period 86400 --namespace AWS/EC2 --statistics Sum --dimensions Name=InstanceId,Value=$INSTANCE_ID --region $REGION --query "Datapoints[].Sum" --output text | awk '{ total += $1; count++ } END { if (count > 0) print total/count; else print 0 }') # --- Classification logic --- STATUS="NORMAL" if (( $(echo "$CPU_AVG < 5" | bc -l) )) && (( $(echo "($NET_IN + $NET_OUT) < 1000000" | bc -l) )); then STATUS="IDLE" elif (( $(echo "$CPU_AVG > 70" | bc -l) )); then STATUS="OVERUTILIZED" fi echo "$INSTANCE_ID,${NAME:-NoName},${TYPE:-Unknown},${CPU_AVG:-0},${NET_IN:-0},${NET_OUT:-0},$STATUS" >> $OUTPUT_FILE echo "✔ $INSTANCE_ID (${NAME:-NoName}) → CPU:${CPU_AVG:-0}% | NET_IN:${NET_IN:-0} | NET_OUT:${NET_OUT:-0} | $STATUS"doneechoecho "✅ Report generated: $OUTPUT_FILE"echo "💡 Tip: Sort by 'Status' column (IDLE / OVERUTILIZED) to prioritize optimization."