Find the 5 cloud bill line items driving most of your spend
For developers who need to cut a cloud bill without spending hours in billing dashboards. This walks you through exporting the current invoice or cost report, grouping spend by service, and producing a top-five list with percentages so you know exactly where to act first.
TL;DR — To cut a cloud bill fast, do not start by tuning instances at random. Export the last full month of costs to CSV, group by service, sort descending, and calculate the cumulative percentage; in most accounts, five line items explain the majority of spend. Reading time: ~5 min
Goal
When you finish, you will have a CSV and a terminal-generated top-five cost report for the last full billing month, including each line item’s amount and share of total spend, so you can target the biggest savings first instead of guessing.
Prerequisites
- Billing access for your cloud provider with permission to view invoices or cost reports and export CSV data
- A shell with standard Unix tools:
bash,sort,awk,sed,head,column python >= 3.9— check withpython3 --version- A recent spreadsheet app is optional, not required
- The provider’s billing export at hand, or access to generate it in your provider’s dashboard (for example: Billing/Cost Management → Reports/Invoices → Export CSV)
- One full closed billing month to analyze; do not use the current partial month unless you explicitly want a partial result
Steps
Step 1: Export the last full month of detailed costs to CSV
In your provider’s billing dashboard, export a detailed cost report for the last closed month with the finest available grouping. Use a CSV that includes at least one service/category column and one cost/amount column.
Exact menu path varies by provider, but the action is:
Billing or Cost Management → Reports or Cost Analysis → Date range: Last full month → Granularity: Monthly or Daily → Grouping: Service/Product if available → Export → CSV
Save the file locally as billing.csv.
What you should see when this succeeds: a CSV file exists locally and the header row contains a service-like column and a numeric cost-like column.
Step 2: Inspect the CSV headers and identify the service and cost columns
Run these commands to print the header and sample rows:
python3 - <<'PY'
import csv
with open('billing.csv', newline='') as f:
r = csv.reader(f)
header = next(r)
print('HEADER:')
for i, c in enumerate(header, 1):
print(f'{i:>3} {c}')
print('\nSAMPLE ROWS:')
f.seek(0)
dr = csv.DictReader(f)
for n, row in zip(range(3), dr):
print(row)
PY
Typical output shape:
HEADER:
1 Date
2 Service
3 Region
4 UsageType
5 Cost
6 Currency
SAMPLE ROWS:
{'Date': '2026-07-01', 'Service': 'Compute', 'Region': 'us-east-1', 'UsageType': 'OnDemand', 'Cost': '184.22', 'Currency': 'USD'}
{'Date': '2026-07-01', 'Service': 'Block Storage', 'Region': 'us-east-1', 'UsageType': 'gp3', 'Cost': '96.40', 'Currency': 'USD'}
{'Date': '2026-07-01', 'Service': 'Data Transfer', 'Region': 'global', 'UsageType': 'Internet egress', 'Cost': '71.09', 'Currency': 'USD'}
What you should see when this succeeds: you know the exact column names to use, for example Service and Cost.
Step 3: Generate a top-five report grouped by service
Run this script, replacing SERVICE_COL and COST_COL with the exact header names from Step 2.
python3 - <<'PY'
import csv
from collections import defaultdict
CSV_FILE = 'billing.csv'
SERVICE_COL = 'Service'
COST_COL = 'Cost'
costs = defaultdict(float)
total = 0.0
with open(CSV_FILE, newline='') as f:
reader = csv.DictReader(f)
missing = [c for c in (SERVICE_COL, COST_COL) if c not in reader.fieldnames]
if missing:
raise SystemExit(f'ERROR: missing columns: {missing}; available={reader.fieldnames}')
for row in reader:
service = (row.get(SERVICE_COL) or '').strip() or '(blank)'
raw = (row.get(COST_COL) or '').replace(',', '').replace('$', '').strip()
if raw == '':
continue
try:
amount = float(raw)
except ValueError:
raise SystemExit(f'ERROR: non-numeric cost value: {raw!r} in row {row}')
costs[service] += amount
total += amount
rows = sorted(costs.items(), key=lambda kv: kv[1], reverse=True)
print('rank,service,amount,pct_of_total,cumulative_pct')
cum = 0.0
for i, (service, amount) in enumerate(rows[:5], 1):
pct = (amount / total * 100.0) if total else 0.0
cum += pct
print(f'{i},{service},{amount:.2f},{pct:.2f},{cum:.2f}')
print(f'TOTAL,,{total:.2f},100.00,100.00')
PY
Typical output shape:
rank,service,amount,pct_of_total,cumulative_pct
1,Compute,1842.20,41.37,41.37
2,Managed Database,963.11,21.63,63.00
3,Block Storage,611.42,13.73,76.73
4,Data Transfer,489.05,10.98,87.71
5,Object Storage,271.88,6.10,93.81
TOTAL,,4451.77,100.00,100.00
What you should see when this succeeds: five services sorted by spend, plus the percentage of total and cumulative percentage.
Step 4: Save the result as a reusable report file
Write the report to disk so you can attach it to a ticket, PR, or incident note.
python3 - <<'PY' > top5-costs.csv
import csv
from collections import defaultdict
CSV_FILE = 'billing.csv'
SERVICE_COL = 'Service'
COST_COL = 'Cost'
costs = defaultdict(float)
total = 0.0
with open(CSV_FILE, newline='') as f:
reader = csv.DictReader(f)
for row in reader:
service = (row.get(SERVICE_COL) or '').strip() or '(blank)'
raw = (row.get(COST_COL) or '').replace(',', '').replace('$', '').strip()
if raw == '':
continue
amount = float(raw)
costs[service] += amount
total += amount
rows = sorted(costs.items(), key=lambda kv: kv[1], reverse=True)
print('rank,service,amount,pct_of_total,cumulative_pct')
cum = 0.0
for i, (service, amount) in enumerate(rows[:5], 1):
pct = (amount / total * 100.0) if total else 0.0
cum += pct
print(f'{i},{service},{amount:.2f},{pct:.2f},{cum:.2f}')
print(f'TOTAL,,{total:.2f},100.00,100.00')
PY
column -s, -t < top5-costs.csv
Typical output shape:
rank service amount pct_of_total cumulative_pct
1 Compute 1842.20 41.37 41.37
2 Managed Database 963.11 21.63 63.00
3 Block Storage 611.42 13.73 76.73
4 Data Transfer 489.05 10.98 87.71
5 Object Storage 271.88 6.10 93.81
TOTAL 4451.77 100.00 100.00
What you should see when this succeeds: a file named top5-costs.csv and a readable table in the terminal.
Step 5: If the top five are too broad, split the biggest one by usage type or region
If Compute or Managed Database is too coarse to act on, export a more detailed CSV or reuse the same one if it already contains UsageType, SKU, Region, or ResourceId. Then rerun the grouping on the biggest service only.
python3 - <<'PY'
import csv
from collections import defaultdict
CSV_FILE = 'billing.csv'
FILTER_SERVICE = 'Compute'
SERVICE_COL = 'Service'
GROUP_COL = 'UsageType'
COST_COL = 'Cost'
costs = defaultdict(float)
total = 0.0
with open(CSV_FILE, newline='') as f:
reader = csv.DictReader(f)
for row in reader:
if (row.get(SERVICE_COL) or '').strip() != FILTER_SERVICE:
continue
group = (row.get(GROUP_COL) or '').strip() or '(blank)'
raw = (row.get(COST_COL) or '').replace(',', '').replace('$', '').strip()
if raw == '':
continue
amount = float(raw)
costs[group] += amount
total += amount
for group, amount in sorted(costs.items(), key=lambda kv: kv[1], reverse=True)[:10]:
print(f'{group},{amount:.2f},{(amount/total*100 if total else 0):.2f}')
PY
What you should see when this succeeds: the largest service is broken into actionable sub-lines such as OnDemand, Snapshot storage, Provisioned IOPS, or Internet egress.
Verify it works
Run these checks:
test -f billing.csv && echo "billing.csv present"
test -f top5-costs.csv && echo "top5-costs.csv present"
head -n 7 top5-costs.csv
Expected output shape:
billing.csv present
top5-costs.csv present
rank,service,amount,pct_of_total,cumulative_pct
1,Compute,1842.20,41.37,41.37
2,Managed Database,963.11,21.63,63.00
3,Block Storage,611.42,13.73,76.73
4,Data Transfer,489.05,10.98,87.71
5,Object Storage,271.88,6.10,93.81
TOTAL,,4451.77,100.00,100.00
You are done when the report shows five ranked line items and their cumulative share of total spend. If the cumulative percentage is above roughly 70-90%, you have the right targets.
Common pitfalls
Exporting the current month instead of the last closed month
Mistake: using a partial month because it is the default dashboard view.
Symptom: top items look wrong or unusually small, and rerunning the report tomorrow changes the ranking.
Fix: set the date range to Last full month or the exact previous month before exporting.
Using amortized/forecasted values mixed with actual charges
Mistake: exporting forecast, amortized commitment charges, or blended values when you wanted actual billed usage.
Symptom: totals do not match the invoice and reserved/committed spend appears smeared across services.
Fix: export actual cost or invoice charges only; if your provider offers multiple cost bases, pick the one that matches the bill you are trying to cut.
Wrong column names in the script
Mistake: leaving SERVICE_COL='Service' and COST_COL='Cost' when your CSV uses names like ProductName or UnblendedCost.
Symptom: the script exits with:
ERROR: missing columns: ['Service', 'Cost']; available=['Date', 'ProductName', 'Region', 'UnblendedCost']
Fix: rerun Step 2 and replace the constants with the exact header names from your CSV.
Currency symbols or thousands separators breaking numeric parsing
Mistake: feeding values like $1,234.56 or 1.234,56 into a parser expecting plain 1234.56.
Symptom: the script exits with ERROR: non-numeric cost value or totals are obviously wrong.
Fix: export raw numeric CSV fields if your provider offers that; otherwise adjust the cleanup line for your locale before float() conversion.
Credits, refunds, or taxes hiding the real spend drivers
Mistake: including tax, support plan, promotional credit, or refund rows in the same grouping as service usage.
Symptom: a negative or non-actionable line item appears in the top five, or the total is much lower than expected.
Fix: filter out rows where the charge type is Tax, Credit, Refund, or Support if your goal is usage reduction rather than invoice reconciliation.
Grouping only by service when one service is still too big to act on
Mistake: stopping at Compute without splitting by usage type, region, or resource.
Symptom: you know the biggest bucket but still cannot decide what to change.
Fix: rerun Step 5 on the biggest service using UsageType, Region, SKU, or ResourceId as GROUP_COL.
This article was written by an AI system and published pending human review. Verify anything you intend to act on.
Have a project in mind?
Get an instant AI price estimate for it, or talk directly to our team.
One email a month on what we learn building with AI