Running EC2 instances averaging under 5% CPU over 30 days, sized down one or two steps in the same family
What does ZopNight detect here?
ZopNight downsizes running EC2 instances whose `CPUUtilization` averages under 5% over 30 days: one size smaller in the same family between 2% and 5%, two sizes smaller below 2%. Memory from the CloudWatch agent can veto the change, and the saving is the real On-Demand rate gap between the current and target sizes.
Signal and threshold
| Field | Value |
|---|---|
| Rule IDs | RC-003 |
| Category | rightsizing |
| Severity | medium |
| Metric | CPUUtilization |
| Threshold | < 5% average (one size), < 2% (two sizes) |
| Evaluation window | 30d |
| Source | ZopNight |
| Permissions used | ec2:DescribeInstances · cloudwatch:GetMetricStatistics |
Where it applies
An instance bills for its size, not its load
EC2 On-Demand pricing is per instance type per
hour. Within a family each size step down usually halves vCPU, memory and price; in US East
(N. Virginia) an m5.xlarge is $0.192 an hour and an m5.large $0.096. An instance that averages a
few percent CPU all month is paying for capacity that sits idle.
AWS notes that when you change the instance type you start paying the rate of the new type, and it points to AWS Compute Optimizer for a type recommendation.
Pulling CPU and memory yourself
aws cloudwatch get-metric-statistics --namespace AWS/EC2 --metric-name CPUUtilization \ --dimensions Name=InstanceId,Value=i-0123456789abcdef0 \ --statistics Average Maximum --period 86400 \ --start-time 2026-08-26T00:00:00Z --end-time 2026-09-25T00:00:00Z
aws cloudwatch get-metric-statistics --namespace CWAgent --metric-name mem_used_percent \ --dimensions Name=InstanceId,Value=i-0123456789abcdef0 \ --statistics Average Maximum --period 86400 \ --start-time 2026-08-26T00:00:00Z --end-time 2026-09-25T00:00:00ZEC2 does not publish memory on its own. It comes from the
CloudWatch agent
as a used-memory percentage (the metric in the second command), in the CWAgent namespace by default.
How the downsize depth is chosen
| 30-day average CPU | Recommendation | Memory rule |
|---|---|---|
| Below 2% | Two sizes smaller, high severity | Blocked if memory, scaled to the smaller size, would exceed 80% |
| 2% to under 5% | One size smaller, medium severity | Blocked if memory is at 60% or more |
| 5% or more | Nothing |
A CPU peak at or above 80% anywhere in the series also blocks the change. An instance stopped for 60% to 99% of the last 7 days gets a cautious one-size step.
Instances the rule hands to other checks
An instance idle for 95% or more of at least 168 hourly readings in the 30 days is left to Idle Running EC2 Instance, since stopping it beats shrinking it. A fully stopped instance belongs to Idle EC2 Instance. GPU families are sized by GPU Instance Low Utilization on GPU metrics instead. With no memory data, memory-optimized families (r, x, z, u, i) and database or broker hosts are skipped; other instances are sized on CPU alone and the finding says memory could not be verified.
Real rates for the current and target size
saving = monthly cost x (1 - target hourly rate / current hourly rate)Both are On-Demand catalog rates for real instance types. If there is no smaller type in the family at the chosen depth, or no rate gap, nothing is shown.
Resizing the instance
- Confirm memory headroom with the agent metrics if they are not already installed.
- Stop the instance, change its type, and start it:
aws ec2 modify-instance-attribute --instance-id i-0123456789abcdef0 --instance-type Value=m5.large - Watch CPU, memory and latency for a few days.
- Revisit after a month; a two-step case may be worth another step.