AWS Cost Explorer: Complete Guide to Usage Analysis, Cost Optimization & Cloud FinOps 2024
Master AWS Cost Explorer with this comprehensive 3500+ word guide covering dashboard navigation, Cost and Usage Reports setup, cost allocation tags, forecasting, boto3 API automation, rightsizing recommendations, Reserved Instance analysis, Savings Plans optimization, and Lambda-based automated reporting.
Managing cloud costs effectively has become one of the most critical challenges facing organizations of all sizes. As AWS environments grow in complexity—spanning multiple accounts, regions, and services—understanding where your money goes and how to optimize spending requires sophisticated tools and systematic approaches. AWS Cost Explorer stands as the cornerstone of AWS cost management, providing powerful visualization, analysis, and forecasting capabilities that enable FinOps teams, cloud architects, and finance professionals to gain actionable insights into their cloud spending.
This comprehensive guide provides an in-depth exploration of AWS Cost Explorer, covering everything from basic dashboard navigation to advanced API automation. Whether you're just starting your cost optimization journey or looking to implement enterprise-grade cost management practices, this guide will equip you with the knowledge and code examples needed to master AWS cost analysis and achieve significant savings.
Understanding AWS Cost Explorer: Foundation and Architecture
AWS Cost Explorer is a free tool available in the AWS Billing and Cost Management console that enables you to visualize, understand, and manage your AWS costs and usage over time. Unlike simple billing statements, Cost Explorer provides interactive analysis capabilities that let you drill down into specific services, accounts, regions, and custom dimensions to understand the drivers of your cloud spending.
Key Capabilities of Cost Explorer
Cost Explorer offers several powerful features that form the foundation of effective cloud cost management:
Interactive Cost Visualization: View your costs and usage patterns through customizable charts and graphs. You can choose between daily or monthly granularity, compare time periods, and identify trends that might otherwise go unnoticed in raw billing data. The visualization engine supports stacked bar charts, line graphs, and area charts to represent your spending data effectively.
Multi-Dimensional Analysis: Group and filter costs by over 20 different dimensions including service, linked account, region, availability zone, instance type, usage type, and custom cost allocation tags. This flexibility enables you to answer complex questions about your spending patterns and understand cost attribution across your organization.
Cost Forecasting: Leverage machine learning-based forecasting to predict future costs based on historical usage patterns. AWS uses sophisticated algorithms that consider seasonality, growth trends, and usage patterns to provide forecasts with confidence intervals, helping with budgeting, capacity planning, and identifying potential cost overruns before they happen.
Savings Recommendations: Access recommendations for Reserved Instances and Savings Plans based on your actual usage patterns, helping you identify opportunities for commitment-based discounts that can reduce costs by up to 72% compared to On-Demand pricing.
API Access: Programmatically access cost and usage data through the Cost Explorer API, enabling custom dashboards, automated reporting, and integration with third-party tools. The API supports all the same filtering and grouping capabilities available in the console.
Understanding Cost Metrics: Blended vs. Unblended
Before diving into Cost Explorer, it's essential to understand the different cost metrics AWS provides:
Unblended Costs: The actual costs incurred for each resource based on the pricing model used (On-Demand, Reserved, Spot). This is typically what you want for detailed cost attribution and chargeback purposes.
Blended Costs: For organizations using consolidated billing, blended costs average the costs across all accounts in the organization. This smooths out the impact of Reserved Instance discounts across accounts that may or may not have used the reserved capacity.
Amortized Costs: Distributes upfront RI or Savings Plans payments across the commitment term, providing a more accurate monthly cost view for financial planning.
Net Unblended Costs: Takes into account any credits or discounts applied to your account, showing what you actually paid.
Enabling Cost Explorer for Your Organization
Before you can use Cost Explorer, you need to enable it in your AWS account. For organizations using AWS Organizations, this should be done from the management (payer) account to ensure visibility across all member accounts.
#!/bin/bash
# Enable Cost Explorer using AWS CLI
# Note: This must be run from the management account
# Check if Cost Explorer is already enabled
aws ce get-cost-and-usage \
--time-period Start=2024-01-01,End=2024-01-02 \
--granularity DAILY \
--metrics "UnblendedCost" \
2>&1
# If you receive an error, Cost Explorer needs to be enabled via the console
# Navigate to: AWS Console > Billing > Cost Explorer > Enable Cost Explorer
# After enabling, wait 24 hours for historical data to become available
# You can then verify access with:
aws ce get-cost-and-usage \
--time-period Start=2024-01-01,End=2024-01-31 \
--granularity MONTHLY \
--metrics "BlendedCost" "UnblendedCost" "UsageQuantity" \
--group-by Type=DIMENSION,Key=SERVICE
# Get cost by linked account for multi-account organizations
aws ce get-cost-and-usage \
--time-period Start=2024-01-01,End=2024-01-31 \
--granularity MONTHLY \
--metrics "UnblendedCost" \
--group-by Type=DIMENSION,Key=LINKED_ACCOUNT
Important Considerations:
- Cost Explorer data becomes available approximately 24 hours after you enable the service
- Historical data going back up to 12 months is available once enabled
- The management account can see costs for all linked accounts in an organization
- Individual linked accounts can only see their own costs unless specifically granted access
- Cost Explorer API calls are charged at $0.01 per request, so optimize your queries
Cost Explorer Dashboard Walkthrough: Navigating the Interface
The Cost Explorer interface is organized around several key views and navigation elements. Understanding how to navigate these effectively is crucial for efficient cost analysis and building a sustainable FinOps practice.
The Main Dashboard View
When you first access Cost Explorer, you'll see a default view showing your monthly costs over the past few months. This view provides immediate insights into:
- Total monthly spend with month-over-month comparison and percentage change
- Top services contributing to your costs with visual breakdown
- Cost trend visualization showing spending patterns over time
- Quick access to saved reports and recommendations
Customizing Your Analysis View
The power of Cost Explorer lies in its customization options. Here's how to configure views for different analysis scenarios:
Time Period Selection: Choose from predefined ranges (last 7 days, last month, year to date) or specify custom date ranges. For trend analysis, longer periods reveal seasonal patterns and growth trajectories. Consider using year-over-year comparisons for mature workloads to account for seasonal business variations.
Granularity Settings:
- Monthly: Best for budgeting, trend analysis, and executive reporting. Use this for long-term planning and comparing year-over-year performance.
- Daily: Essential for identifying anomalies and investigating specific incidents. Daily granularity helps you pinpoint exactly when costs spiked.
- Hourly: Available through the API for detailed operational analysis. Useful for understanding workload patterns and optimizing scheduled tasks.
Grouping Dimensions: Group your costs by various dimensions to answer specific questions:
Service: Which AWS services drive the most spending?Linked Account: How do costs distribute across accounts?Region: What's the geographic distribution of spend?Instance Type: Which EC2 instance families dominate usage?Usage Type: What specific usage patterns exist?Operation: Which operations (e.g., RunInstances, GetObject) generate costs?Purchase Option: Distribution between On-Demand, Reserved, Spot, and Savings Plans
Advanced Filtering Techniques
Effective cost analysis requires filtering to focus on specific aspects of your infrastructure:
Example Filter Configurations:
1. Production Environment Analysis:
Filter: Tag: Environment = "Production"
Group by: Service
Time: Last 6 months, Monthly
Use Case: Understand production infrastructure costs for capacity planning
2. Development Cost Tracking:
Filter: Tag: Environment = "Development"
AND Linked Account IN [dev-account-1, dev-account-2]
Group by: Usage Type
Time: Last 30 days, Daily
Use Case: Identify development waste and optimize sandbox environments
3. EC2 Deep Dive:
Filter: Service = "Amazon Elastic Compute Cloud - Compute"
Group by: Instance Type
Time: Last 3 months, Monthly
Use Case: Identify rightsizing opportunities and instance family optimization
4. Data Transfer Analysis:
Filter: Usage Type Group = "Data Transfer"
Group by: Region
Time: Last 12 months, Monthly
Use Case: Understand cross-region and internet egress patterns
5. Untagged Resource Identification:
Filter: Tag: CostCenter = "" (empty/untagged)
Group by: Service
Time: Last 30 days
Use Case: Identify resources missing required cost allocation tags
Creating and Saving Custom Reports
Cost Explorer allows you to save frequently used views as reports for quick access:
- Configure your desired filters, groupings, and time period
- Click "Save as new report" and provide a descriptive name
- Reports are saved to your account and accessible from the "Saved Reports" menu
- Share report links with team members (they must have appropriate IAM permissions)
Setting Up AWS Cost and Usage Reports (CUR)
While Cost Explorer provides excellent visualization and interactive analysis, the AWS Cost and Usage Reports (CUR) deliver the most granular cost data available. CUR provides hourly or daily line items for each resource, enabling detailed analysis that goes beyond what Cost Explorer's interface offers.
Understanding CUR vs. Cost Explorer
| Feature | Cost Explorer | Cost and Usage Reports |
|---|---|---|
| Data Granularity | Daily/Monthly | Hourly/Daily |
| Resource-Level Detail | Limited | Complete with resource IDs |
| Custom Integration | API-based | S3 + Athena/Redshift/QuickSight |
| Historical Data | 12 months | Unlimited (stored in S3) |
| Cost | Free + API charges | S3 storage costs only |
| Query Flexibility | Predefined dimensions | Full SQL with Athena |
| Real-time Data | ~24 hour delay | Up to 3x daily refresh |
Creating a Cost and Usage Report with Terraform
Here's a comprehensive Terraform configuration to set up CUR with Athena integration for SQL-based cost analysis:
# Terraform configuration for AWS Cost and Usage Reports with Athena integration
provider "aws" {
region = "us-east-1" # CUR must be created in us-east-1
}
# S3 bucket for CUR data
resource "aws_s3_bucket" "cur_bucket" {
bucket = "company-cost-and-usage-reports-${data.aws_caller_identity.current.account_id}"
tags = {
Purpose = "cost-management"
ManagedBy = "terraform"
Environment = "shared"
}
}
resource "aws_s3_bucket_versioning" "cur_bucket" {
bucket = aws_s3_bucket.cur_bucket.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "cur_bucket" {
bucket = aws_s3_bucket.cur_bucket.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
resource "aws_s3_bucket_lifecycle_configuration" "cur_lifecycle" {
bucket = aws_s3_bucket.cur_bucket.id
rule {
id = "transition-to-ia"
status = "Enabled"
transition {
days = 90
storage_class = "STANDARD_IA"
}
transition {
days = 365
storage_class = "GLACIER"
}
expiration {
days = 2555 # 7 years for compliance
}
}
}
resource "aws_s3_bucket_policy" "cur_bucket_policy" {
bucket = aws_s3_bucket.cur_bucket.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowCURDelivery"
Effect = "Allow"
Principal = {
Service = "billingreports.amazonaws.com"
}
Action = [
"s3:GetBucketAcl",
"s3:GetBucketPolicy"
]
Resource = aws_s3_bucket.cur_bucket.arn
Condition = {
StringEquals = {
"aws:SourceAccount" = data.aws_caller_identity.current.account_id
"aws:SourceArn" = "arn:aws:cur:us-east-1:${data.aws_caller_identity.current.account_id}:definition/*"
}
}
},
{
Sid = "AllowCURWrite"
Effect = "Allow"
Principal = {
Service = "billingreports.amazonaws.com"
}
Action = "s3:PutObject"
Resource = "${aws_s3_bucket.cur_bucket.arn}/*"
Condition = {
StringEquals = {
"aws:SourceAccount" = data.aws_caller_identity.current.account_id
"aws:SourceArn" = "arn:aws:cur:us-east-1:${data.aws_caller_identity.current.account_id}:definition/*"
}
}
}
]
})
}
# Cost and Usage Report Definition
resource "aws_cur_report_definition" "main" {
report_name = "comprehensive-cost-report"
time_unit = "HOURLY"
format = "Parquet"
compression = "Parquet"
additional_schema_elements = ["RESOURCES", "SPLIT_COST_ALLOCATION_DATA"]
s3_bucket = aws_s3_bucket.cur_bucket.id
s3_region = "us-east-1"
s3_prefix = "cur"
report_versioning = "OVERWRITE_REPORT"
refresh_closed_reports = true
additional_artifacts = ["ATHENA"]
depends_on = [aws_s3_bucket_policy.cur_bucket_policy]
}
# Athena workgroup for CUR queries
resource "aws_athena_workgroup" "cur_workgroup" {
name = "cost-analysis-workgroup"
configuration {
enforce_workgroup_configuration = true
publish_cloudwatch_metrics_enabled = true
result_configuration {
output_location = "s3://${aws_s3_bucket.cur_bucket.id}/athena-results/"
encryption_configuration {
encryption_option = "SSE_S3"
}
}
bytes_scanned_cutoff_per_query = 10737418240 # 10 GB limit per query
}
tags = {
Purpose = "cost-analysis"
}
}
# Glue database for CUR (created automatically by Athena integration)
resource "aws_glue_catalog_database" "cur_database" {
name = "cost_and_usage_reports"
}
data "aws_caller_identity" "current" {}
output "cur_bucket_name" {
value = aws_s3_bucket.cur_bucket.id
description = "S3 bucket containing Cost and Usage Reports"
}
output "athena_workgroup" {
value = aws_athena_workgroup.cur_workgroup.name
description = "Athena workgroup for cost queries"
}
Querying CUR Data with Athena
Once your CUR is set up and data is flowing (typically takes 24-48 hours for first delivery), you can use Athena to run complex SQL queries against your cost data:
-- Top 10 most expensive resources in the last month
SELECT
line_item_resource_id,
line_item_product_code AS service,
product_product_name AS product_name,
SUM(line_item_unblended_cost) AS total_cost,
SUM(line_item_usage_amount) AS total_usage
FROM cost_and_usage_reports.cur_table
WHERE
line_item_usage_start_date >= date_add('month', -1, current_date)
AND line_item_resource_id IS NOT NULL
AND line_item_resource_id != ''
GROUP BY line_item_resource_id, line_item_product_code, product_product_name
ORDER BY total_cost DESC
LIMIT 10;
-- Daily cost trend by service for the last 30 days
SELECT
date_trunc('day', line_item_usage_start_date) AS usage_date,
line_item_product_code AS service,
SUM(line_item_unblended_cost) AS daily_cost
FROM cost_and_usage_reports.cur_table
WHERE line_item_usage_start_date >= date_add('day', -30, current_date)
GROUP BY date_trunc('day', line_item_usage_start_date), line_item_product_code
ORDER BY usage_date, daily_cost DESC;
-- EC2 spending by instance type and purchase option
SELECT
product_instance_type AS instance_type,
pricing_term AS purchase_option,
SUM(line_item_unblended_cost) AS total_cost,
SUM(line_item_usage_amount) AS total_hours
FROM cost_and_usage_reports.cur_table
WHERE
line_item_product_code = 'AmazonEC2'
AND product_instance_type IS NOT NULL
AND line_item_usage_start_date >= date_add('month', -1, current_date)
GROUP BY product_instance_type, pricing_term
ORDER BY total_cost DESC;
-- Identify untagged resources and their costs
SELECT
line_item_product_code AS service,
line_item_resource_id AS resource_id,
SUM(line_item_unblended_cost) AS untagged_cost
FROM cost_and_usage_reports.cur_table
WHERE
line_item_usage_start_date >= date_add('month', -1, current_date)
AND (resource_tags_user_cost_center IS NULL OR resource_tags_user_cost_center = '')
AND line_item_resource_id IS NOT NULL
GROUP BY line_item_product_code, line_item_resource_id
HAVING SUM(line_item_unblended_cost) > 10
ORDER BY untagged_cost DESC
LIMIT 50;
Implementing Cost Allocation Tags for Detailed Tracking
Cost allocation tags are the foundation of effective cloud cost management. They enable you to categorize and track AWS costs by project, team, environment, application, or any other dimension relevant to your organization. Without proper tagging, you cannot implement accurate showback, chargeback, or cost attribution.
Designing Your Tagging Strategy
A well-designed tagging strategy should address these key dimensions and be enforceable across your organization:
| Tag Key | Purpose | Example Values | Required? |
|---|---|---|---|
| Environment | Separate prod/dev costs | production, staging, development, sandbox | Yes |
| Project | Track project spending | customer-portal, data-pipeline, mobile-api | Yes |
| CostCenter | Financial allocation | CC-1234, marketing-ops, engineering-platform | Yes |
| Owner | Accountability | team-platform, john.smith@company.com | Yes |
| Application | Application tracking | payment-service, analytics-api, user-auth | Recommended |
| BusinessUnit | Department allocation | engineering, marketing, sales, finance | Recommended |
| Compliance | Regulatory requirements | pci, hipaa, sox, gdpr | When applicable |
Enforcing Tags with AWS Organizations Tag Policies
Before managing cost allocation tags, implement tag policies to enforce consistency:
{
"tags": {
"Environment": {
"tag_key": {
"@@assign": "Environment"
},
"tag_value": {
"@@assign": [
"production",
"staging",
"development",
"sandbox"
]
},
"enforced_for": {
"@@assign": [
"ec2:instance",
"ec2:volume",
"rds:db",
"s3:bucket",
"lambda:function"
]
}
},
"CostCenter": {
"tag_key": {
"@@assign": "CostCenter"
},
"enforced_for": {
"@@assign": [
"ec2:instance",
"rds:db"
]
}
}
}
}
Managing Cost Allocation Tags with Python boto3
import boto3
from botocore.exceptions import ClientError
from typing import List, Dict
def manage_cost_allocation_tags() -> Dict:
"""
Manage AWS Cost Allocation Tags programmatically.
This script lists existing tags, activates recommended tags,
and provides a summary of tag coverage.
"""
ce_client = boto3.client('ce')
results = {
'active_tags': [],
'inactive_tags': [],
'activated_tags': [],
'errors': []
}
# Get list of active cost allocation tags
try:
response = ce_client.list_cost_allocation_tags(
Status='Active',
MaxResults=100
)
active_tags = response.get('CostAllocationTags', [])
results['active_tags'] = [tag['TagKey'] for tag in active_tags]
print(f"Currently active cost allocation tags: {len(active_tags)}")
for tag in active_tags:
print(f" - {tag['TagKey']}: Status={tag['Status']}, Type={tag.get('Type', 'User')}")
except ClientError as e:
error_msg = f"Error listing active tags: {e}"
print(error_msg)
results['errors'].append(error_msg)
return results
# Get inactive tags
try:
inactive_response = ce_client.list_cost_allocation_tags(
Status='Inactive',
MaxResults=100
)
inactive_tags = inactive_response.get('CostAllocationTags', [])
results['inactive_tags'] = [tag['TagKey'] for tag in inactive_tags]
print(f"\nInactive tags available for activation: {len(inactive_tags)}")
for tag in inactive_tags[:10]: # Show first 10
print(f" - {tag['TagKey']}")
except ClientError as e:
error_msg = f"Error listing inactive tags: {e}"
print(error_msg)
results['errors'].append(error_msg)
return results
# Recommended tags to activate (customize based on your organization)
recommended_tags = [
'Environment',
'Project',
'CostCenter',
'Owner',
'Application',
'BusinessUnit',
'Team'
]
# Activate recommended tags that are currently inactive
tags_to_activate = []
for tag in inactive_tags:
if tag['TagKey'] in recommended_tags:
tags_to_activate.append({
'TagKey': tag['TagKey'],
'Status': 'Active'
})
if tags_to_activate:
try:
print(f"\nActivating {len(tags_to_activate)} cost allocation tags...")
ce_client.update_cost_allocation_tags_status(
CostAllocationTagsStatus=tags_to_activate
)
results['activated_tags'] = [t['TagKey'] for t in tags_to_activate]
print("Tags activated successfully!")
for tag in tags_to_activate:
print(f" - Activated: {tag['TagKey']}")
except ClientError as e:
error_msg = f"Error activating tags: {e}"
print(error_msg)
results['errors'].append(error_msg)
else:
print("\nNo recommended tags need activation.")
return results
def get_tag_coverage_report() -> None:
"""
Generate a report showing tag coverage across resources.
Helps identify resources missing required cost allocation tags.
"""
ce_client = boto3.client('ce')
from datetime import datetime, timedelta
end_date = datetime.now().strftime('%Y-%m-%d')
start_date = (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d')
# Get costs grouped by a required tag to see coverage
for tag_key in ['Environment', 'CostCenter', 'Project']:
try:
response = ce_client.get_cost_and_usage(
TimePeriod={'Start': start_date, 'End': end_date},
Granularity='MONTHLY',
Metrics=['UnblendedCost'],
GroupBy=[{'Type': 'TAG', 'Key': tag_key}]
)
tagged_cost = 0
untagged_cost = 0
for result in response['ResultsByTime']:
for group in result['Groups']:
tag_value = group['Keys'][0]
cost = float(group['Metrics']['UnblendedCost']['Amount'])
if tag_value and tag_value != f'{tag_key}