Back to Playbooks

Multi-Cloud Reserved Instance Coverage Report

Analyze Reserved Instance, Savings Plan, and Committed Use Discount coverage across Azure, AWS, GCP, and OCI. Identify on-demand waste, get purchase recommendations, and calculate potential savings of 30-72%.

20 min read
AzureAWSGCPOCIFinOpsReserved InstancesSavings PlansCost Optimization
Share on X

The #1 FinOps Lever You're Probably Ignoring

Reserved Instances (RIs) and Savings Plans offer 30-72% discounts compared to on-demand pricing. Yet most organizations run at less than 50% coverage, leaving massive savings on the table.

This playbook gives you automated tools to analyze your current coverage, identify gaps, and calculate exactly how much you could save.

Typical Savings by Commitment Type

30-40%

1-Year Reserved

Lower commitment, moderate savings

55-72%

3-Year Reserved

Maximum savings for stable workloads

20-30%

Savings Plans

Flexible across instance types

What This Tool Analyzes

Current Coverage %

What percentage of your eligible compute is covered by RIs or Savings Plans

On-Demand Waste

Spend that could be converted to reserved pricing for immediate savings

Utilization Analysis

Are your existing reservations being fully used, or are you wasting commitments?

Purchase Recommendations

Specific RIs to buy based on your actual usage patterns and break-even analysis

Prerequisites

  • Python 3.8 or higher
  • Azure CLI installed and authenticated (az login)
  • Azure SDK: pip install azure-identity azure-mgmt-consumption azure-mgmt-reservations azure-mgmt-costmanagement
  • Cost Management Reader or Billing Reader role on subscription/billing scope
  • For OCI: OCI CLI configured with appropriate IAM policies

Quick Start

# Install required packages
pip install azure-identity azure-mgmt-consumption azure-mgmt-reservations azure-mgmt-costmanagement pandas tabulate

# Login to Azure
az login

# Run the coverage report
python ri_coverage_report.py --subscription "your-subscription-id"

# For all subscriptions in a billing account
python ri_coverage_report.py --billing-account "your-billing-account-id"

The Complete Script

This Python script connects to Azure Cost Management APIs to analyze your RI coverage, utilization, and generate purchase recommendations.

ri_coverage_report.pypython
#!/usr/bin/env python3
"""
Reserved Instance Coverage Report
Analyzes RI/Savings Plan coverage and generates purchase recommendations
"""

import argparse
import json
from datetime import datetime, timedelta
from azure.identity import DefaultAzureCredential
from azure.mgmt.consumption import ConsumptionManagementClient
from azure.mgmt.reservations import AzureReservationAPI
from azure.mgmt.costmanagement import CostManagementClient
import pandas as pd
from tabulate import tabulate

class RICoverageAnalyzer:
    def __init__(self, credential, subscription_id=None, billing_account_id=None):
        self.credential = credential
        self.subscription_id = subscription_id
        self.billing_account_id = billing_account_id

        if subscription_id:
            self.consumption_client = ConsumptionManagementClient(
                credential, subscription_id
            )
            self.cost_client = CostManagementClient(credential)

        self.reservation_client = AzureReservationAPI(credential)

    def get_reservation_summaries(self, grain='daily', days=30):
        """Get reservation utilization summaries"""
        end_date = datetime.utcnow()
        start_date = end_date - timedelta(days=days)

        scope = f"/subscriptions/{self.subscription_id}"

        try:
            summaries = list(
                self.consumption_client.reservations_summaries.list(
                    scope=scope,
                    grain=grain,
                    start_date=start_date.strftime('%Y-%m-%d'),
                    end_date=end_date.strftime('%Y-%m-%d')
                )
            )
            return summaries
        except Exception as e:
            print(f"Note: Could not fetch reservation summaries: {e}")
            return []

    def get_reservation_recommendations(self, scope_type='Single', look_back='Last30Days'):
        """Get RI purchase recommendations from Azure"""
        scope = f"/subscriptions/{self.subscription_id}"

        try:
            recommendations = list(
                self.consumption_client.reservation_recommendations.list(
                    scope=scope,
                    filter=f"properties/scope eq '{scope_type}' and properties/lookBackPeriod eq '{look_back}'"
                )
            )
            return recommendations
        except Exception as e:
            print(f"Note: Could not fetch recommendations: {e}")
            return []

    def get_cost_by_pricing_model(self, days=30):
        """Analyze costs by pricing model (On-Demand vs Reserved)"""
        end_date = datetime.utcnow()
        start_date = end_date - timedelta(days=days)

        scope = f"/subscriptions/{self.subscription_id}"

        query = {
            "type": "ActualCost",
            "timeframe": "Custom",
            "timePeriod": {
                "from": start_date.strftime('%Y-%m-%dT00:00:00Z'),
                "to": end_date.strftime('%Y-%m-%dT23:59:59Z')
            },
            "dataset": {
                "granularity": "None",
                "aggregation": {
                    "totalCost": {
                        "name": "Cost",
                        "function": "Sum"
                    }
                },
                "grouping": [
                    {
                        "type": "Dimension",
                        "name": "PricingModel"
                    },
                    {
                        "type": "Dimension",
                        "name": "ServiceName"
                    }
                ]
            }
        }

        try:
            result = self.cost_client.query.usage(scope=scope, parameters=query)
            return self._parse_cost_query_result(result)
        except Exception as e:
            print(f"Error querying costs: {e}")
            return None

    def _parse_cost_query_result(self, result):
        """Parse the cost query result into a DataFrame"""
        if not result.rows:
            return pd.DataFrame()

        columns = [col.name for col in result.columns]
        df = pd.DataFrame(result.rows, columns=columns)

        # Convert cost to float
        if 'Cost' in df.columns:
            df['Cost'] = pd.to_numeric(df['Cost'], errors='coerce')

        return df

    def calculate_coverage_metrics(self, cost_data):
        """Calculate RI coverage metrics from cost data"""
        if cost_data is None or cost_data.empty:
            return None

        # Group by pricing model
        pricing_summary = cost_data.groupby('PricingModel')['Cost'].sum()

        total_cost = pricing_summary.sum()
        on_demand = pricing_summary.get('OnDemand', 0) + pricing_summary.get('On Demand', 0)
        reserved = pricing_summary.get('Reservation', 0) + pricing_summary.get('Reserved', 0)
        savings_plan = pricing_summary.get('SavingsPlan', 0) + pricing_summary.get('Savings Plan', 0)
        spot = pricing_summary.get('Spot', 0)

        covered = reserved + savings_plan
        coverable = on_demand + covered  # Excludes spot

        coverage_pct = (covered / coverable * 100) if coverable > 0 else 0

        return {
            'total_cost': total_cost,
            'on_demand_cost': on_demand,
            'reserved_cost': reserved,
            'savings_plan_cost': savings_plan,
            'spot_cost': spot,
            'coverage_percentage': coverage_pct,
            'potential_savings_low': on_demand * 0.30,  # 30% conservative
            'potential_savings_high': on_demand * 0.55,  # 55% aggressive (3yr)
        }

    def get_utilization_details(self):
        """Get detailed utilization of existing reservations"""
        try:
            reservations = list(
                self.reservation_client.reservation_order.list()
            )

            utilization_data = []
            for order in reservations:
                order_id = order.name

                # Get reservations in this order
                try:
                    items = list(
                        self.reservation_client.reservation.list(
                            reservation_order_id=order_id
                        )
                    )

                    for item in items:
                        utilization_data.append({
                            'order_id': order_id,
                            'reservation_id': item.name,
                            'display_name': getattr(item, 'display_name', 'N/A'),
                            'sku': getattr(item.sku, 'name', 'N/A') if item.sku else 'N/A',
                            'quantity': getattr(item, 'quantity', 0),
                            'term': getattr(item.properties, 'term', 'N/A') if hasattr(item, 'properties') else 'N/A',
                            'provisioning_state': getattr(item, 'provisioning_state', 'N/A'),
                        })
                except Exception as e:
                    print(f"Could not get details for order {order_id}: {e}")

            return utilization_data
        except Exception as e:
            print(f"Error fetching reservations: {e}")
            return []

    def format_recommendations(self, recommendations):
        """Format recommendations for display"""
        if not recommendations:
            return []

        formatted = []
        for rec in recommendations:
            props = rec.properties if hasattr(rec, 'properties') else rec

            formatted.append({
                'sku': getattr(props, 'sku_name', 'N/A') if hasattr(props, 'sku_name') else getattr(props, 'recommended_resource_type', 'N/A'),
                'location': getattr(props, 'location', 'N/A'),
                'recommended_quantity': getattr(props, 'recommended_quantity', 0),
                'term': getattr(props, 'term', 'N/A'),
                'cost_with_no_ri': getattr(props, 'cost_with_no_reserved_instances', 0),
                'net_savings': getattr(props, 'net_savings', 0),
                'first_usage_date': str(getattr(props, 'first_usage_date', 'N/A')),
            })

        return formatted

    def generate_report(self, days=30):
        """Generate comprehensive RI coverage report"""
        print("=" * 70)
        print("RESERVED INSTANCE COVERAGE REPORT")
        print(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
        print(f"Analysis Period: Last {days} days")
        print("=" * 70)

        # 1. Cost Analysis by Pricing Model
        print("\n📊 COST ANALYSIS BY PRICING MODEL")
        print("-" * 50)

        cost_data = self.get_cost_by_pricing_model(days=days)
        metrics = self.calculate_coverage_metrics(cost_data)

        if metrics:
            print("Total Compute Cost:      $%s" % format(metrics['total_cost'], ',.2f'))
            print("On-Demand Cost:          $%s" % format(metrics['on_demand_cost'], ',.2f'))
            print("Reserved Cost:           $%s" % format(metrics['reserved_cost'], ',.2f'))
            print("Savings Plan Cost:       $%s" % format(metrics['savings_plan_cost'], ',.2f'))
            print("Spot Cost:               $%s" % format(metrics['spot_cost'], ',.2f'))
            print("\n🎯 COVERAGE: %.1f%%" % metrics['coverage_percentage'])
            print("\n💰 POTENTIAL ANNUAL SAVINGS:")
            print("   Conservative (1yr RI): $%s" % format(metrics['potential_savings_low'] * 12, ',.2f'))
            print("   Aggressive (3yr RI):   $%s" % format(metrics['potential_savings_high'] * 12, ',.2f'))
        else:
            print("Could not retrieve cost data. Check permissions.")

        # 2. Existing Reservation Utilization
        print("\n📈 EXISTING RESERVATION INVENTORY")
        print("-" * 50)

        utilization = self.get_utilization_details()
        if utilization:
            df = pd.DataFrame(utilization)
            print(tabulate(df, headers='keys', tablefmt='grid', showindex=False))
        else:
            print("No existing reservations found or could not retrieve data.")

        # 3. Purchase Recommendations
        print("\n🛒 PURCHASE RECOMMENDATIONS")
        print("-" * 50)

        recommendations = self.get_reservation_recommendations()
        formatted_recs = self.format_recommendations(recommendations)

        if formatted_recs:
            df = pd.DataFrame(formatted_recs)
            print(tabulate(df, headers='keys', tablefmt='grid', showindex=False))

            total_potential_savings = sum(r.get('net_savings', 0) for r in formatted_recs)
            print("\nTotal Recommended Savings: $%s/year" % format(total_potential_savings, ',.2f'))
        else:
            print("No recommendations available. This could mean:")
            print("  - You already have optimal coverage")
            print("  - Usage is too variable for commitments")
            print("  - Insufficient data (need 30+ days of usage)")

        # 4. Action Items
        print("\n✅ RECOMMENDED ACTIONS")
        print("-" * 50)

        if metrics and metrics['coverage_percentage'] < 70:
            print("⚠️  Coverage is %.1f%% - target is 70-80%%" % metrics['coverage_percentage'])
            print("   → Review purchase recommendations above")
            print("   → Start with 1-year terms for new workloads")
            print("   → Consider 3-year terms for stable, long-running VMs")
        elif metrics and metrics['coverage_percentage'] < 50:
            print("🚨 Coverage is critically low at %.1f%%" % metrics['coverage_percentage'])
            print("   → Immediate action required")
            print("   → Prioritize largest on-demand spenders")
        else:
            print("✅ Coverage looks healthy. Review quarterly to maintain.")

        print("\n" + "=" * 70)

        return dict(
            metrics=metrics,
            utilization=utilization,
            recommendations=formatted_recs
        )


def main():
    parser = argparse.ArgumentParser(
        description='Analyze Reserved Instance coverage and get recommendations'
    )
    parser.add_argument(
        '--subscription', '-s',
        help='Azure Subscription ID',
        required=True
    )
    parser.add_argument(
        '--billing-account', '-b',
        help='Azure Billing Account ID (for multi-subscription analysis)',
        default=None
    )
    parser.add_argument(
        '--days', '-d',
        type=int,
        default=30,
        help='Number of days to analyze (default: 30)'
    )
    parser.add_argument(
        '--output', '-o',
        help='Output file path for JSON report',
        default=None
    )

    args = parser.parse_args()

    print("🔐 Authenticating with Azure...")
    credential = DefaultAzureCredential()

    analyzer = RICoverageAnalyzer(
        credential=credential,
        subscription_id=args.subscription,
        billing_account_id=args.billing_account
    )

    report = analyzer.generate_report(days=args.days)

    if args.output:
        # Convert to JSON-serializable format
        output_data = dict(
            generated_at=datetime.now().isoformat(),
            subscription_id=args.subscription,
            analysis_days=args.days,
            metrics=report['metrics'],
            recommendations=report['recommendations']
        )

        with open(args.output, 'w') as f:
            json.dump(output_data, f, indent=2, default=str)

        print("\n📄 Report saved to: " + args.output)


if __name__ == "__main__":
    main()

OCI Reserved Capacity Analyzer

For OCI environments, use this companion script to analyze compute capacity reservations:

oci_capacity_report.pypython
#!/usr/bin/env python3
"""
OCI Reserved Capacity Coverage Report
Analyzes capacity reservations and commitment coverage in OCI
"""

import oci
from datetime import datetime, timedelta
import pandas as pd
from tabulate import tabulate

def get_oci_config():
    """Load OCI config from default location"""
    return oci.config.from_file()

def analyze_capacity_reservations(config, compartment_id):
    """Analyze OCI capacity reservations"""
    compute_client = oci.core.ComputeClient(config)

    # Get all capacity reservations
    reservations = compute_client.list_compute_capacity_reservations(
        compartment_id=compartment_id
    ).data

    results = []
    for reservation in reservations:
        # Get reservation details
        details = compute_client.get_compute_capacity_reservation(
            capacity_reservation_id=reservation.id
        ).data

        results.append(dict(
            name=details.display_name,
            availability_domain=details.availability_domain,
            reserved_count=details.reserved_instance_count,
            used_count=details.used_instance_count,
            utilization_pct=(details.used_instance_count / details.reserved_instance_count * 100)
                              if details.reserved_instance_count > 0 else 0,
            state=details.lifecycle_state,
            time_created=str(details.time_created)
        ))

    return results

def get_cost_analysis(config, tenant_id, compartment_id, days=30):
    """Get cost data from OCI Cost Analysis"""
    usage_client = oci.usage_api.UsageapiClient(config)

    end_time = datetime.utcnow()
    start_time = end_time - timedelta(days=days)

    # Query for compute costs by pricing model
    request = oci.usage_api.models.RequestSummarizedUsagesDetails(
        tenant_id=tenant_id,
        time_usage_started=start_time,
        time_usage_ended=end_time,
        granularity="MONTHLY",
        query_type="COST",
        compartment_depth=3,
        group_by=["service", "skuPartNumber"],
        filter=oci.usage_api.models.Filter(
            dimensions=[
                oci.usage_api.models.Dimension(
                    key="service",
                    value="COMPUTE"
                )
            ]
        )
    )

    response = usage_client.request_summarized_usages(request)
    return response.data.items

def generate_oci_report(compartment_id, tenant_id):
    """Generate OCI capacity coverage report"""
    config = get_oci_config()

    print("=" * 70)
    print("OCI RESERVED CAPACITY REPORT")
    print("Generated: " + datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
    print("=" * 70)

    # Capacity Reservations
    print("\n📊 CAPACITY RESERVATIONS")
    print("-" * 50)

    reservations = analyze_capacity_reservations(config, compartment_id)

    if reservations:
        df = pd.DataFrame(reservations)
        print(tabulate(df, headers='keys', tablefmt='grid', showindex=False))

        # Calculate overall utilization
        total_reserved = sum(r['reserved_count'] for r in reservations)
        total_used = sum(r['used_count'] for r in reservations)
        overall_util = (total_used / total_reserved * 100) if total_reserved > 0 else 0

        print("\n🎯 Overall Utilization: %.1f%%" % overall_util)

        if overall_util < 80:
            print("⚠️  Consider reducing reservations or migrating workloads")
    else:
        print("No capacity reservations found.")

    print("\n" + "=" * 70)

if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument('--compartment', '-c', required=True)
    parser.add_argument('--tenant', '-t', required=True)
    args = parser.parse_args()

    generate_oci_report(args.compartment, args.tenant)

AWS Reserved Instance & Savings Plan Analyzer

For AWS environments, use this script to analyze EC2 Reserved Instances and Compute Savings Plans coverage:

aws_ri_coverage_report.pypython
#!/usr/bin/env python3
"""
AWS Reserved Instance & Savings Plan Coverage Report
Analyzes RI/SP coverage and generates purchase recommendations for AWS
"""

import boto3
from datetime import datetime, timedelta
import pandas as pd
from tabulate import tabulate

class AWSRICoverageAnalyzer:
    def __init__(self, profile_name=None, region='us-east-1'):
        session_args = dict(region_name=region)
        if profile_name:
            session_args['profile_name'] = profile_name

        self.session = boto3.Session(**session_args)
        self.ce_client = self.session.client('ce')  # Cost Explorer
        self.ec2_client = self.session.client('ec2')

    def get_ri_coverage(self, days=30):
        """Get RI coverage from Cost Explorer"""
        end_date = datetime.utcnow()
        start_date = end_date - timedelta(days=days)

        try:
            response = self.ce_client.get_reservation_coverage(
                TimePeriod=dict(
                    Start=start_date.strftime('%Y-%m-%d'),
                    End=end_date.strftime('%Y-%m-%d')
                ),
                Granularity='MONTHLY',
                Metrics=['Hour']
            )
            return response.get('Total', {})
        except Exception as e:
            print("Error fetching RI coverage: %s" % str(e))
            return None

    def get_savings_plan_coverage(self, days=30):
        """Get Savings Plan coverage"""
        end_date = datetime.utcnow()
        start_date = end_date - timedelta(days=days)

        try:
            response = self.ce_client.get_savings_plans_coverage(
                TimePeriod=dict(
                    Start=start_date.strftime('%Y-%m-%d'),
                    End=end_date.strftime('%Y-%m-%d')
                ),
                Granularity='MONTHLY'
            )
            return response.get('SavingsPlansCoverages', [])
        except Exception as e:
            print("Error fetching SP coverage: %s" % str(e))
            return []

    def get_ri_utilization(self, days=30):
        """Get RI utilization metrics"""
        end_date = datetime.utcnow()
        start_date = end_date - timedelta(days=days)

        try:
            response = self.ce_client.get_reservation_utilization(
                TimePeriod=dict(
                    Start=start_date.strftime('%Y-%m-%d'),
                    End=end_date.strftime('%Y-%m-%d')
                ),
                Granularity='MONTHLY'
            )
            return response.get('Total', {})
        except Exception as e:
            print("Error fetching RI utilization: %s" % str(e))
            return None

    def get_ri_recommendations(self, service='Amazon Elastic Compute Cloud - Compute'):
        """Get RI purchase recommendations"""
        try:
            response = self.ce_client.get_reservation_purchase_recommendation(
                Service=service,
                LookbackPeriodInDays='THIRTY_DAYS',
                TermInYears='ONE_YEAR',
                PaymentOption='NO_UPFRONT'
            )
            return response.get('Recommendations', [])
        except Exception as e:
            print("Error fetching recommendations: %s" % str(e))
            return []

    def get_savings_plan_recommendations(self):
        """Get Savings Plan purchase recommendations"""
        try:
            response = self.ce_client.get_savings_plans_purchase_recommendation(
                SavingsPlansType='COMPUTE_SP',
                TermInYears='ONE_YEAR',
                PaymentOption='NO_UPFRONT',
                LookbackPeriodInDays='LAST_30_DAYS'
            )
            return response.get('SavingsPlansPurchaseRecommendation', {})
        except Exception as e:
            print("Error fetching SP recommendations: %s" % str(e))
            return {}

    def get_current_reservations(self):
        """Get current EC2 Reserved Instances"""
        try:
            response = self.ec2_client.describe_reserved_instances(
                Filters=[dict(Name='state', Values=['active'])]
            )

            reservations = []
            for ri in response.get('ReservedInstances', []):
                reservations.append(dict(
                    id=ri['ReservedInstancesId'],
                    instance_type=ri['InstanceType'],
                    count=ri['InstanceCount'],
                    scope=ri.get('Scope', 'Region'),
                    offering=ri['OfferingType'],
                    end_date=str(ri['End']),
                    state=ri['State']
                ))
            return reservations
        except Exception as e:
            print("Error fetching reservations: %s" % str(e))
            return []

    def generate_report(self, days=30):
        """Generate comprehensive AWS RI coverage report"""
        print("=" * 70)
        print("AWS RESERVED INSTANCE COVERAGE REPORT")
        print("Generated: " + datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
        print("Analysis Period: Last %d days" % days)
        print("=" * 70)

        # 1. RI Coverage
        print("\n📊 RESERVED INSTANCE COVERAGE")
        print("-" * 50)

        ri_coverage = self.get_ri_coverage(days=days)
        if ri_coverage:
            coverage_hours = ri_coverage.get('CoverageHours', {})
            coverage_pct = float(coverage_hours.get('CoverageHoursPercentage', 0))
            on_demand_hours = float(coverage_hours.get('OnDemandHours', 0))
            reserved_hours = float(coverage_hours.get('ReservedHours', 0))

            print("Coverage Percentage:    %.1f%%" % coverage_pct)
            print("On-Demand Hours:        %s" % format(on_demand_hours, ',.0f'))
            print("Reserved Hours:         %s" % format(reserved_hours, ',.0f'))

        # 2. Savings Plan Coverage
        print("\n📈 SAVINGS PLAN COVERAGE")
        print("-" * 50)

        sp_coverage = self.get_savings_plan_coverage(days=days)
        if sp_coverage:
            for period in sp_coverage:
                coverage = period.get('Coverage', {})
                sp_pct = float(coverage.get('CoveragePercentage', 0))
                print("Savings Plan Coverage:  %.1f%%" % sp_pct)

        # 3. RI Utilization
        print("\n⚡ RESERVATION UTILIZATION")
        print("-" * 50)

        utilization = self.get_ri_utilization(days=days)
        if utilization:
            util_pct = float(utilization.get('UtilizationPercentage', 0))
            print("Utilization:            %.1f%%" % util_pct)

            if util_pct < 80:
                print("⚠️  Low utilization - consider modifying or selling unused RIs")

        # 4. Current Reservations
        print("\n📋 ACTIVE RESERVATIONS")
        print("-" * 50)

        reservations = self.get_current_reservations()
        if reservations:
            df = pd.DataFrame(reservations)
            print(tabulate(df, headers='keys', tablefmt='grid', showindex=False))
        else:
            print("No active reservations found")

        # 5. Recommendations
        print("\n🛒 PURCHASE RECOMMENDATIONS")
        print("-" * 50)

        ri_recs = self.get_ri_recommendations()
        if ri_recs:
            for rec in ri_recs:
                details = rec.get('RecommendationDetails', [])
                for detail in details[:5]:  # Top 5
                    instance_type = detail.get('InstanceDetails', {}).get('EC2InstanceDetails', {}).get('InstanceType', 'N/A')
                    savings = detail.get('EstimatedMonthlySavingsAmount', '0')
                    print("  %s: $%s/month savings" % (instance_type, savings))

        sp_recs = self.get_savings_plan_recommendations()
        if sp_recs:
            estimated_savings = sp_recs.get('EstimatedMonthlySavingsAmount', 0)
            commitment = sp_recs.get('HourlyCommitmentToPurchase', 0)
            print("\nSavings Plan Recommendation:")
            print("  Hourly Commitment: $%s" % commitment)
            print("  Est. Monthly Savings: $%s" % estimated_savings)

        print("\n" + "=" * 70)


def main():
    import argparse
    parser = argparse.ArgumentParser(
        description='Analyze AWS RI and Savings Plan coverage'
    )
    parser.add_argument('--profile', '-p', help='AWS profile name', default=None)
    parser.add_argument('--region', '-r', help='AWS region', default='us-east-1')
    parser.add_argument('--days', '-d', type=int, default=30, help='Days to analyze')
    args = parser.parse_args()

    print("🔐 Authenticating with AWS...")
    analyzer = AWSRICoverageAnalyzer(
        profile_name=args.profile,
        region=args.region
    )
    analyzer.generate_report(days=args.days)


if __name__ == "__main__":
    main()

AWS Prerequisites

# Install boto3
pip install boto3 pandas tabulate

# Configure AWS credentials
aws configure

# Or use a named profile
aws configure --profile my-finops-profile

# Required IAM permissions:
# - ce:GetReservationCoverage
# - ce:GetReservationUtilization
# - ce:GetReservationPurchaseRecommendation
# - ce:GetSavingsPlansCoverage
# - ce:GetSavingsPlansPurchaseRecommendation
# - ec2:DescribeReservedInstances

GCP Committed Use Discount Analyzer

For Google Cloud, analyze Committed Use Discounts (CUDs) for Compute Engine and other services:

gcp_cud_coverage_report.pypython
#!/usr/bin/env python3
"""
GCP Committed Use Discount (CUD) Coverage Report
Analyzes CUD coverage and generates recommendations for Google Cloud
"""

from google.cloud import billing_v1
from google.cloud import compute_v1
from google.cloud import bigquery
from datetime import datetime, timedelta
import pandas as pd
from tabulate import tabulate

class GCPCUDAnalyzer:
    def __init__(self, project_id, billing_account_id=None):
        self.project_id = project_id
        self.billing_account_id = billing_account_id
        self.compute_client = compute_v1.RegionCommitmentsClient()
        self.bq_client = bigquery.Client(project=project_id)

    def get_active_commitments(self):
        """Get all active CUDs across regions"""
        commitments = []
        regions_client = compute_v1.RegionsClient()

        # List all regions
        regions = regions_client.list(project=self.project_id)

        for region in regions:
            try:
                region_commitments = self.compute_client.list(
                    project=self.project_id,
                    region=region.name
                )

                for commitment in region_commitments:
                    if commitment.status == 'ACTIVE':
                        commitments.append(dict(
                            name=commitment.name,
                            region=region.name,
                            plan=commitment.plan,  # TWELVE_MONTH or THIRTY_SIX_MONTH
                            status=commitment.status,
                            start_timestamp=str(commitment.start_timestamp),
                            end_timestamp=str(commitment.end_timestamp),
                            resources=self._format_resources(commitment.resources)
                        ))
            except Exception as e:
                print("Error listing commitments in %s: %s" % (region.name, str(e)))

        return commitments

    def _format_resources(self, resources):
        """Format commitment resources for display"""
        formatted = []
        for resource in resources:
            formatted.append("%s: %s" % (resource.type_, resource.amount))
        return ", ".join(formatted) if formatted else "N/A"

    def get_cost_data_from_bigquery(self, days=30):
        """Query billing export for cost analysis by SKU"""
        # This requires BigQuery billing export to be enabled
        query = """
        SELECT
            service.description as service,
            sku.description as sku,
            SUM(cost) as total_cost,
            SUM(CASE WHEN credits.type = 'COMMITTED_USE_DISCOUNT'
                THEN credits.amount ELSE 0 END) as cud_credit,
            SUM(CASE WHEN credits.type = 'SUSTAINED_USE_DISCOUNT'
                THEN credits.amount ELSE 0 END) as sud_credit
        FROM `%s.billing_export.gcp_billing_export_v1_*`
        LEFT JOIN UNNEST(credits) as credits
        WHERE _PARTITIONTIME >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL %d DAY)
            AND service.description LIKE '%%Compute Engine%%'
        GROUP BY service.description, sku.description
        ORDER BY total_cost DESC
        LIMIT 20
        """ % (self.project_id, days)

        try:
            df = self.bq_client.query(query).to_dataframe()
            return df
        except Exception as e:
            print("BigQuery error (ensure billing export is enabled): %s" % str(e))
            return None

    def calculate_coverage_metrics(self, cost_data):
        """Calculate CUD coverage from billing data"""
        if cost_data is None or cost_data.empty:
            return None

        total_cost = cost_data['total_cost'].sum()
        cud_savings = abs(cost_data['cud_credit'].sum())
        sud_savings = abs(cost_data['sud_credit'].sum())

        # Estimate what could be saved with full CUD coverage
        # CUDs typically save 57% for 3-year, 37% for 1-year
        potential_additional = (total_cost - cud_savings) * 0.40

        return dict(
            total_compute_cost=total_cost,
            cud_savings=cud_savings,
            sud_savings=sud_savings,
            coverage_percentage=(cud_savings / total_cost * 100) if total_cost > 0 else 0,
            potential_additional_savings=potential_additional
        )

    def get_recommendations(self):
        """Get CUD recommendations from Recommender API"""
        from google.cloud import recommender_v1

        recommendations = []
        try:
            client = recommender_v1.RecommenderClient()
            parent = "projects/%s/locations/global/recommenders/google.compute.commitment.UsageCommitmentRecommender" % self.project_id

            recs = client.list_recommendations(parent=parent)
            for rec in recs:
                recommendations.append(dict(
                    name=rec.name,
                    description=rec.description,
                    priority=rec.priority.name,
                    primary_impact=str(rec.primary_impact)
                ))
        except Exception as e:
            print("Could not fetch recommendations: %s" % str(e))

        return recommendations

    def generate_report(self, days=30):
        """Generate comprehensive GCP CUD coverage report"""
        print("=" * 70)
        print("GCP COMMITTED USE DISCOUNT REPORT")
        print("Generated: " + datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
        print("Project: " + self.project_id)
        print("=" * 70)

        # 1. Active Commitments
        print("\n📋 ACTIVE COMMITMENTS")
        print("-" * 50)

        commitments = self.get_active_commitments()
        if commitments:
            df = pd.DataFrame(commitments)
            print(tabulate(df, headers='keys', tablefmt='grid', showindex=False))
            print("\nTotal active commitments: %d" % len(commitments))
        else:
            print("No active commitments found")

        # 2. Cost Analysis (requires BigQuery billing export)
        print("\n📊 COST ANALYSIS")
        print("-" * 50)

        cost_data = self.get_cost_data_from_bigquery(days=days)
        metrics = self.calculate_coverage_metrics(cost_data)

        if metrics:
            print("Total Compute Cost:      $%s" % format(metrics['total_compute_cost'], ',.2f'))
            print("CUD Savings Applied:     $%s" % format(metrics['cud_savings'], ',.2f'))
            print("SUD Savings Applied:     $%s" % format(metrics['sud_savings'], ',.2f'))
            print("\n🎯 CUD COVERAGE: %.1f%%" % metrics['coverage_percentage'])
            print("\n💰 POTENTIAL ADDITIONAL SAVINGS:")
            print("   With more CUDs:       $%s/month" % format(metrics['potential_additional_savings'], ',.2f'))
        else:
            print("Enable BigQuery billing export for detailed cost analysis")

        # 3. Recommendations
        print("\n🛒 RECOMMENDATIONS")
        print("-" * 50)

        recommendations = self.get_recommendations()
        if recommendations:
            for rec in recommendations:
                print("• [%s] %s" % (rec['priority'], rec['description']))
        else:
            print("No recommendations available")
            print("Tip: Check Cloud Console > Recommendations for CUD suggestions")

        # 4. CUD Savings Reference
        print("\n📈 GCP CUD SAVINGS REFERENCE")
        print("-" * 50)
        print("1-Year Commitment:  37%% discount")
        print("3-Year Commitment:  57%% discount")
        print("\nTip: Use spend-based CUDs for flexibility across machine types")

        print("\n" + "=" * 70)


def main():
    import argparse
    parser = argparse.ArgumentParser(
        description='Analyze GCP Committed Use Discount coverage'
    )
    parser.add_argument('--project', '-p', required=True, help='GCP Project ID')
    parser.add_argument('--billing-account', '-b', help='Billing Account ID')
    parser.add_argument('--days', '-d', type=int, default=30, help='Days to analyze')
    args = parser.parse_args()

    print("🔐 Authenticating with GCP...")
    analyzer = GCPCUDAnalyzer(
        project_id=args.project,
        billing_account_id=args.billing_account
    )
    analyzer.generate_report(days=args.days)


if __name__ == "__main__":
    main()

GCP Prerequisites

# Install Google Cloud SDK and Python libraries
pip install google-cloud-billing google-cloud-compute google-cloud-bigquery google-cloud-recommender pandas tabulate

# Authenticate
gcloud auth application-default login

# Enable required APIs
gcloud services enable compute.googleapis.com
gcloud services enable cloudbilling.googleapis.com
gcloud services enable bigquery.googleapis.com
gcloud services enable recommender.googleapis.com

# For cost analysis, enable BigQuery billing export:
# Console > Billing > Billing export > BigQuery export

# Required IAM roles:
# - roles/compute.viewer
# - roles/billing.viewer
# - roles/bigquery.dataViewer
# - roles/recommender.computeViewer

Multi-Cloud Commitment Comparison

CloudCommitment Type1-Year Savings3-Year SavingsFlexibility
AzureReserved Instances~36%~60%Exchange allowed
AzureSavings Plans~20%~33%Cross-region, cross-size
AWSReserved Instances~40%~60%Convertible option
AWSCompute Savings Plans~30%~52%Any instance family
GCPCommitted Use Discounts~37%~57%Spend-based option
OCICapacity Reservations~35%~50%Guaranteed capacity

* Savings percentages are approximate and vary by instance type, region, and payment option

Understanding the Output

Coverage Percentage

The percentage of your compute spend that's covered by commitments (RIs + Savings Plans).

<50%

Critical - Act Now

50-70%

Needs Improvement

70-85%

Healthy Target

Utilization Rate

How much of your existing reservations are being used. Low utilization (<80%) means you're paying for capacity you're not using. Consider exchanging or selling unused RIs.

Break-Even Analysis

Recommendations include break-even points. If a VM has been running consistently for the break-even period (typically 7-9 months for 1yr, 14-18 months for 3yr), the RI will save money.

RI Purchase Decision Framework

Buy 3-Year RIs When:

  • • Workload has been stable for 6+ months
  • • No planned migration or decommission
  • • Maximum savings is priority (55-72% discount)
  • • Can pay upfront or partial upfront

Buy 1-Year RIs When:

  • • New workloads proving stability
  • • Uncertain long-term requirements
  • • Want flexibility to resize/exchange
  • • 30-40% savings is acceptable

Use Savings Plans When:

  • • Workloads change instance types frequently
  • • Multi-region deployments
  • • Want automatic application of discounts
  • • Prefer flexibility over maximum savings

Stay On-Demand When:

  • • Workload runs less than 7 months/year
  • • Highly variable or unpredictable usage
  • • Planning to migrate to different service
  • • Dev/test environments with inconsistent usage

Sample Report Output

======================================================================
RESERVED INSTANCE COVERAGE REPORT
Generated: 2025-12-15 14:32:17
Analysis Period: Last 30 days
======================================================================

📊 COST ANALYSIS BY PRICING MODEL
--------------------------------------------------
Total Compute Cost:      $127,543.00
On-Demand Cost:          $89,280.10
Reserved Cost:           $31,456.90
Savings Plan Cost:       $6,806.00
Spot Cost:               $0.00

🎯 COVERAGE: 30.0%

💰 POTENTIAL ANNUAL SAVINGS:
   Conservative (1yr RI): $321,408.36
   Aggressive (3yr RI):   $589,248.66

📈 EXISTING RESERVATION INVENTORY
--------------------------------------------------
+------------------+----------------+----------+-------+
| reservation_id   | sku            | quantity | term  |
+==================+================+==========+=======+
| res-001          | Standard_D4s_v3| 10       | P3Y   |
| res-002          | Standard_E8s_v3| 5        | P1Y   |
| res-003          | Standard_D2s_v3| 20       | P1Y   |
+------------------+----------------+----------+-------+

🛒 PURCHASE RECOMMENDATIONS
--------------------------------------------------
+------------------+------------+----------+-------+---------------+
| sku              | location   | quantity | term  | net_savings   |
+==================+============+==========+=======+===============+
| Standard_D8s_v5  | eastus     | 15       | P1Y   | $42,840.00    |
| Standard_E4s_v5  | eastus     | 8        | P1Y   | $18,432.00    |
| Standard_D4s_v5  | westus2    | 12       | P3Y   | $67,392.00    |
+------------------+------------+----------+-------+---------------+

Total Recommended Savings: $128,664.00/year

✅ RECOMMENDED ACTIONS
--------------------------------------------------
⚠️  Coverage is 30.0% - target is 70-80%
   → Review purchase recommendations above
   → Start with 1-year terms for new workloads
   → Consider 3-year terms for stable, long-running VMs

======================================================================

Automate Monthly Reports

Set up automated monthly RI coverage reports using Azure Functions or a simple cron job:

Cron job examplebash
# Run on the 1st of every month at 8 AM
0 8 1 * * /usr/bin/python3 /path/to/ri_coverage_report.py \
  --subscription "your-sub-id" \
  --output "/reports/ri-coverage-$(date +\%Y\%m).json" \
  2>&1 | mail -s "Monthly RI Coverage Report" finops-team@company.com

Azure DevOps Pipeline

azure-pipelines.ymlyaml
trigger:
  schedules:
    - cron: "0 8 1 * *"  # 1st of month at 8 AM
      displayName: Monthly RI Report
      branches:
        include:
          - main

pool:
  vmImage: 'ubuntu-latest'

steps:
- task: AzureCLI@2
  inputs:
    azureSubscription: 'Your-Service-Connection'
    scriptType: 'bash'
    scriptLocation: 'inlineScript'
    inlineScript: |
      pip install azure-identity azure-mgmt-consumption azure-mgmt-reservations pandas tabulate
      python ri_coverage_report.py --subscription $(SUBSCRIPTION_ID) --output ri-report.json

- task: PublishBuildArtifacts@1
  inputs:
    pathtoPublish: 'ri-report.json'
    artifactName: 'ri-coverage-report'

Pro Tips

Don't over-commit. Target 70-80% coverage, not 100%. Leave room for variable workloads and new projects.

Review quarterly. Run this report every quarter and adjust commitments as your infrastructure evolves.

Stack discounts. Azure Hybrid Benefit + RIs can stack for up to 80%+ savings on Windows VMs.

Exchange unused RIs. Azure allows exchanging RIs for different SKUs. Don't let unused capacity go to waste.

Real-World Savings Example

Before Optimization
23%
RI Coverage
$1.2M/year
Compute Spend
After Optimization
78%
RI Coverage
$720K/year
Compute Spend
Annual Savings Achieved
$480,000
40% reduction in compute costs