[PATCH v4 5/8] aws: add cloud billing support with make cloud-bill
Luis Chamberlain <[email protected]>
| Newsgroups | dev.linux.lists.kdevops |
|---|---|
| Message-ID | <[email protected]> |
Add AWS cost tracking functionality to quickly check monthly spending. This includes my personal hack scripts for monitoring AWS costs: - scripts/aws-costs.sh: Queries AWS Cost Explorer for current month - scripts/aws-parse-costs.py: Parses and displays costs by service - make cloud-bill: Show cloud provider costs (currently AWS only) - make cloud-bill-aws: Show detailed AWS costs for current month The scripts provide: - Total monthly cost to date - Breakdown by AWS service - Daily average spending - Projected monthly cost (when mid-month) This is useful for monitoring cloud spending during development and testing, especially when running expensive instances or long tests. Generated-by: Claude AI Signed-off-by: Luis Chamberlain <[email protected]> --- scripts/aws-costs.sh | 39 ++++++++++ scripts/aws-parse-costs.py | 98 ++++++++++++++++++++++++++ scripts/dynamic-cloud-kconfig.Makefile | 13 +++- 3 files changed, 149 insertions(+), 1 deletion(-) create mode 100755 scripts/aws-costs.sh create mode 100755 scripts/aws-parse-costs.py diff --git a/scripts/aws-costs.sh b/scripts/aws-costs.sh new file mode 100755 index 00000000..e2298008 --- /dev/null +++ b/scripts/aws-costs.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# SPDX-License-Identifier: copyleft-next-0.3.1 +# +# AWS cost tracking script - quick hack to check AWS spending +# This queries AWS Cost Explorer to get current month's costs + +set -e + +# Get the first and last day of the current month +FIRST_DAY=$(date +%Y-%m-01) +LAST_DAY=$(date -d "$FIRST_DAY +1 month -1 day" +%Y-%m-%d) +TODAY=$(date +%Y-%m-%d) + +# If we're still in the current month, use today as the end date +if [[ "$TODAY" < "$LAST_DAY" ]]; then + END_DATE="$TODAY" +else + END_DATE="$LAST_DAY" +fi + +echo "Fetching AWS costs from $FIRST_DAY to $END_DATE..." >&2 + +# Query AWS Cost Explorer +aws ce get-cost-and-usage \ + --time-period Start=$FIRST_DAY,End=$END_DATE \ + --granularity MONTHLY \ + --metrics UnblendedCost \ + --group-by Type=DIMENSION,Key=SERVICE \ + --output json > cost.json + +# Parse and display the results +if [ -f cost.json ]; then + echo "Cost data saved to cost.json" >&2 + echo "Parsing costs..." >&2 + python3 scripts/aws-parse-costs.py cost.json +else + echo "Error: Failed to retrieve cost data" >&2 + exit 1 +fi \ No newline at end of file diff --git a/scripts/aws-parse-costs.py b/scripts/aws-parse-costs.py new file mode 100755 index 00000000..7f3256a7 --- /dev/null +++ b/scripts/aws-parse-costs.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: copyleft-next-0.3.1 +# +# Parse AWS Cost Explorer JSON output and display costs + +import json +import sys +from datetime import datetime + + +def parse_costs(filename): + """Parse AWS Cost Explorer JSON output.""" + try: + with open(filename, 'r') as f: + data = json.load(f) + except FileNotFoundError: + print(f"Error: File {filename} not found", file=sys.stderr) + sys.exit(1) + except json.JSONDecodeError as e: + print(f"Error parsing JSON: {e}", file=sys.stderr) + sys.exit(1) + + # Extract time period + if 'ResultsByTime' in data and data['ResultsByTime']: + result = data['ResultsByTime'][0] + time_period = result.get('TimePeriod', {}) + start = time_period.get('Start', 'Unknown') + end = time_period.get('End', 'Unknown') + + print(f"\nAWS Cost Report") + print(f"Period: {start} to {end}") + print("=" * 60) + + # Get total cost + total = result.get('Total', {}) + if 'UnblendedCost' in total: + total_amount = float(total['UnblendedCost'].get('Amount', 0)) + currency = total['UnblendedCost'].get('Unit', 'USD') + print(f"\nTotal Cost: ${total_amount:.2f} {currency}") + + # Get costs by service + groups = result.get('Groups', []) + if groups: + print("\nCosts by Service:") + print("-" * 40) + + # Sort by cost (descending) + sorted_groups = sorted(groups, + key=lambda x: float(x['Metrics']['UnblendedCost']['Amount']), + reverse=True) + + for group in sorted_groups: + service = group['Keys'][0] if group.get('Keys') else 'Unknown' + metrics = group.get('Metrics', {}) + if 'UnblendedCost' in metrics: + amount = float(metrics['UnblendedCost'].get('Amount', 0)) + if amount > 0.01: # Only show services with costs > $0.01 + print(f" {service:30} ${amount:10.2f}") + + # Show total at the bottom + print("-" * 40) + if 'UnblendedCost' in total: + print(f" {'TOTAL':30} ${total_amount:10.2f}") + + # Calculate daily average + try: + start_date = datetime.strptime(start, '%Y-%m-%d') + end_date = datetime.strptime(end, '%Y-%m-%d') + days = (end_date - start_date).days + if days > 0 and 'UnblendedCost' in total: + daily_avg = total_amount / days + print(f"\nDaily Average: ${daily_avg:.2f}") + + # Project monthly cost if we're mid-month + today = datetime.now() + if end_date.date() == today.date() and start_date.day == 1: + days_in_month = 30 # Approximate + projected = daily_avg * days_in_month + print(f"Projected Monthly: ${projected:.2f}") + except ValueError: + pass + + else: + print("No cost data available in the response", file=sys.stderr) + sys.exit(1) + + +def main(): + """Main function.""" + if len(sys.argv) != 2: + print("Usage: aws-parse-costs.py <cost.json>", file=sys.stderr) + sys.exit(1) + + parse_costs(sys.argv[1]) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/scripts/dynamic-cloud-kconfig.Makefile b/scripts/dynamic-cloud-kconfig.Makefile index ed2d5366..0ec14966 100644 --- a/scripts/dynamic-cloud-kconfig.Makefile +++ b/scripts/dynamic-cloud-kconfig.Makefile @@ -72,6 +72,8 @@ cloud-config-help: @echo "cloud-update-aws - refreshes AWS data (clears cache and regenerates)" @echo "clean-cloud-config - removes all generated cloud kconfig files" @echo "cloud-list-all - list all cloud instances for configured provider" + @echo "cloud-bill - show current month's cloud provider costs" + @echo "cloud-bill-aws - show AWS costs for current month" HELP_TARGETS += cloud-config-help @@ -88,6 +90,15 @@ cloud-list-all: $(Q)chmod +x scripts/cloud_list_all.sh $(Q)scripts/cloud_list_all.sh +# Cloud billing targets +cloud-bill-aws: + $(Q)chmod +x scripts/aws-costs.sh + $(Q)scripts/aws-costs.sh + +cloud-bill: cloud-bill-aws + $(Q)echo "" + $(Q)echo "Note: Only AWS billing is currently supported" + PHONY += cloud-config cloud-config-lambdalabs cloud-config-aws cloud-update cloud-update-aws PHONY += clean-cloud-config clean-cloud-config-lambdalabs clean-cloud-config-aws -PHONY += cloud-config-help cloud-list-all +PHONY += cloud-config-help cloud-list-all cloud-bill cloud-bill-aws -- 2.51.0