AWS cost calculation and tracking library for Go
costflow is a Go library for calculating and tracking AWS costs with real-time precision. It provides the definitive source for AWS pricing data and supports institutional discounts, credits, and custom overrides.
Part of the ResearchComputing ecosystem.
- Real-time AWS Pricing: Fetch current pricing from AWS Pricing API with caching
- Multi-Service Support: Compute (EC2), Storage (EBS, EFS, S3), Data Transfer
- Institutional Discounts: Support for EDPs, PPAs, credits, and service-specific waivers
- Discount Stacking: Global, service-level, and resource-specific discounts
- State Tracking: Track resource states (running, stopped, terminated) for accurate cost calculations
- Multi-Region: Region-aware pricing lookups
- Utilization Tracking: Calculate running hours, utilization percentages, and cost savings
- Spot Pricing: Support for spot instance pricing
- Reserved Instance: Calculate RI amortization
- Provider-Agnostic Core: Extensible pricing provider interface
go get github.com/researchcomputing/costflowpackage main
import (
"fmt"
"time"
"github.com/researchcomputing/costflow/pkg/calculator"
"github.com/researchcomputing/costflow/pkg/pricing"
)
func main() {
// Create AWS pricing provider with caching
provider := pricing.NewAWS("us-east-1",
pricing.WithCache(24*time.Hour),
)
// Create calculator
calc := calculator.New(provider)
// Calculate compute costs
cost, err := calc.CalculateCompute(calculator.ComputeRequest{
InstanceType: "t3.medium",
Hours: 5.5,
Region: "us-east-1",
})
fmt.Printf("Cost: $%.4f\n", cost.Total)
// Output: Cost: $0.2288
}// Load discount configuration
discounts, err := pricing.LoadDiscounts("discounts.yaml")
// Apply discounts to pricing provider
provider := pricing.NewAWS("us-east-1",
pricing.WithCache(24*time.Hour),
pricing.WithDiscounts(discounts),
)
calc := calculator.New(provider)
cost, _ := calc.CalculateCompute(calculator.ComputeRequest{
InstanceType: "t3.medium",
Hours: 5.5,
})
fmt.Printf("Base cost: $%.4f\n", cost.BaseTotal)
fmt.Printf("After discounts: $%.4f\n", cost.Total)
fmt.Printf("Savings: $%.4f (%.1f%%)\n",
cost.BaseTotal - cost.Total,
cost.DiscountPercent,
)import (
"github.com/researchcomputing/costflow/pkg/state"
)
// Track instance states
tracker := state.NewTracker()
// Record state changes
tracker.RecordState("i-abc123", state.StateRunning, time.Now())
// Later...
tracker.RecordState("i-abc123", state.StateStopped, time.Now().Add(5*time.Hour))
// Calculate costs based on actual running time
history, _ := tracker.GetStateHistory("i-abc123", state.TimeWindow{
Start: startTime,
End: endTime,
})
cost, _ := calc.CalculateComputeFromStates(calculator.ComputeRequest{
InstanceType: "t3.medium",
States: history,
})
fmt.Printf("Running hours: %.2f\n", cost.RunningHours)
fmt.Printf("Utilization: %.1f%%\n", cost.Utilization)
fmt.Printf("Cost: $%.4f\n", cost.Total)costflow supports complex discount scenarios via YAML configuration:
# Global discount from Enterprise Discount Program (EDP)
global:
discount_percent: 15.0
effective_date: "2025-01-01"
expiration_date: "2026-12-31"
description: "Enterprise Discount Program - 15% global"
# Service-specific discounts (Private Pricing Agreement)
services:
- service: EC2
discount_percent: 20.0
description: "PPA for EC2 - 20% discount"
- service: EBS
discount_percent: 10.0
description: "PPA for EBS storage - 10% discount"
# Credits
credits:
- name: "AWS Promotional Credits"
amount: 5000.00
remaining: 3500.00
expires: "2025-12-31"
applies_to:
- EC2
- EBS
- S3
- name: "Research Grant Credits"
amount: 10000.00
remaining: 10000.00
expires: "2026-06-30"
applies_to: "*" # All services
# Service credits (like global data egress waiver)
service_credits:
- service: DataTransfer
credit_type: egress_waiver
description: "Global data egress waiver - credits back on following invoice"
percent: 100.0 # Full waiver
max_credit_per_month: 1000.00
# Instance family specific discounts
instance_families:
- family: "c7g.*" # Graviton instances
discount_percent: 5.0
description: "Additional 5% off ARM Graviton"
- family: "t4g.*"
discount_percent: 5.0
description: "Additional 5% off Graviton burstable"
# Regional variations
regions:
- region: "us-west-2"
services:
- service: EC2
discount_percent: 2.0
description: "West coast datacenter discount"Discounts stack in this order:
- Base AWS rate (from Pricing API or static table)
- Global discount (e.g., EDP 15%)
- Service discount (e.g., EC2 PPA 20%)
- Instance family discount (e.g., Graviton +5%)
- Regional discount (e.g., us-west-2 +2%)
- Credits applied (promotional, research grants)
- Service credits (applied as credit on invoice, tracked separately)
Example calculation:
Base EC2 rate: $0.0416/hour (t3.medium)
After global (15%): $0.0354/hour
After EC2 service (20%): $0.0283/hour
After Graviton (5%): N/A (t3 is Intel)
After regional (2%): $0.0277/hour
Promotional credits: Applied to total
costflow/
├── pkg/
│ ├── calculator/ # Cost calculation engine
│ │ ├── calculator.go # Main calculator
│ │ ├── compute.go # EC2 cost calculations
│ │ ├── storage.go # EBS/EFS/S3 calculations
│ │ └── transfer.go # Data transfer calculations
│ │
│ ├── pricing/ # Pricing providers
│ │ ├── provider.go # Provider interface
│ │ ├── aws.go # AWS Pricing API client
│ │ ├── static.go # Static rate tables
│ │ ├── cache.go # Caching layer
│ │ ├── discounts.go # Discount application logic
│ │ └── overrides.go # Custom pricing overrides
│ │
│ ├── state/ # State tracking
│ │ ├── tracker.go # State change tracker
│ │ ├── history.go # Historical state queries
│ │ ├── machine.go # State machine validation
│ │ └── types.go # State types and constants
│ │
│ └── types/ # Common types
│ ├── cost.go # Cost structures
│ ├── resource.go # Resource types
│ └── time.go # Time window utilities
│
└── examples/
├── basic/ # Basic usage examples
├── discounts/ # Discount scenarios
├── state-tracking/ # State-based tracking
└── multi-region/ # Multi-region calculations
// PricingProvider provides pricing data
type PricingProvider interface {
GetComputeRate(region, instanceType string) (float64, error)
GetStorageRate(region, storageType string, sizeGB float64) (float64, error)
GetTransferRate(region string, transferType TransferType) (float64, error)
}
// DiscountProvider applies discounts to base rates
type DiscountProvider interface {
ApplyDiscount(cost BaseCost, resource Resource) (DiscountedCost, error)
GetEffectiveRate(baseRate float64, resource Resource) (float64, error)
}
// StateTracker tracks resource state changes
type StateTracker interface {
RecordState(resourceID string, state State, timestamp time.Time) error
GetStateHistory(resourceID string, window TimeWindow) ([]StateChange, error)
GetCurrentState(resourceID string) (State, error)
}
// Storage interface for persistence (optional, apps implement)
type Storage interface {
SaveState(resourceID string, states []StateChange) error
LoadStates(resourceID string) ([]StateChange, error)
}Calculate costs for GPU-enabled workspaces that users start and stop:
// Track workspace lifecycle
tracker := state.NewTracker()
// User launches workspace
tracker.RecordState("ws-123", state.StateRunning, launchTime)
// User stops workspace for lunch
tracker.RecordState("ws-123", state.StateStopped, stopTime)
// User restarts
tracker.RecordState("ws-123", state.StateRunning, restartTime)
// End of day - calculate costs
history, _ := tracker.GetStateHistory("ws-123", state.Today())
cost, _ := calc.CalculateComputeFromStates(calculator.ComputeRequest{
InstanceType: "g4dn.xlarge",
States: history,
})
fmt.Printf("Hours actually running: %.2f\n", cost.RunningHours)
fmt.Printf("Cost: $%.2f\n", cost.Total)
fmt.Printf("Savings vs 24/7: $%.2f\n", cost.SavingsVs24x7)Calculate costs for batch computation jobs:
// Job submitted
jobStart := time.Now()
tracker.RecordState("job-456", state.StateRunning, jobStart)
// Job completes
jobEnd := time.Now()
tracker.RecordState("job-456", state.StateTerminated, jobEnd)
// Calculate total cost
duration := jobEnd.Sub(jobStart)
cost, _ := calc.CalculateCompute(calculator.ComputeRequest{
InstanceType: "c7a.16xlarge", // AMD EPYC
Hours: duration.Hours(),
SpotPrice: true, // Using spot instances
})
fmt.Printf("Job duration: %v\n", duration)
fmt.Printf("Spot cost: $%.2f\n", cost.Total)
fmt.Printf("Savings vs on-demand: $%.2f (%.1f%%)\n",
cost.OnDemandTotal - cost.Total,
(1 - cost.Total/cost.OnDemandTotal) * 100,
)Track costs for Jupyter/RStudio environments with EBS storage:
// Calculate compute + storage
computeCost, _ := calc.CalculateCompute(calculator.ComputeRequest{
InstanceType: "t4g.medium",
Hours: actualRunningHours,
})
storageCost, _ := calc.CalculateStorage(calculator.StorageRequest{
StorageType: "gp3",
SizeGB: 100,
Days: 30,
})
totalCost := computeCost.Total + storageCost.Total
fmt.Printf("Compute: $%.2f\n", computeCost.Total)
fmt.Printf("Storage: $%.2f\n", storageCost.Total)
fmt.Printf("Total: $%.2f\n", totalCost)costflow fetches real-time pricing from the AWS Price List API:
- Automatically handles region-specific pricing
- Includes latest price changes
- Cached locally (default 24 hours)
- Fallback to static rates on API failure
For offline usage or testing, costflow includes static rate tables:
provider := pricing.NewStatic(pricing.StaticRates{
"us-east-1": {
Compute: map[string]float64{
"t3.medium": 0.0416,
"c5.xlarge": 0.17,
"g4dn.xlarge": 0.526,
},
Storage: map[string]float64{
"gp3": 0.08, // per GB-month
"gp2": 0.10,
"io2": 0.125,
},
},
})Override specific rates for testing or custom pricing agreements:
provider := pricing.NewAWS("us-east-1")
// Override specific instance type
overridden := pricing.WithOverride(provider, pricing.Overrides{
"t3.medium": pricing.RateOverride{
Rate: 0.0350, // Custom negotiated rate
Reason: "Special agreement",
},
})
calc := calculator.New(overridden)costflow includes comprehensive test coverage:
# Run all tests
go test ./...
# Run with coverage
go test -cover ./...
# Run with race detection
go test -race ./...
# Run specific package
go test ./pkg/calculator
# Run benchmarks
go test -bench=. ./...Contributions welcome! Please see CONTRIBUTING.md.
# Clone repository
git clone https://github.com/researchcomputing/costflow.git
cd costflow
# Install dependencies
go mod download
# Run tests
go test ./...
# Run linter
golangci-lint run- Core pricing provider interface
- AWS Pricing API integration
- Basic cost calculation (EC2, EBS)
- Discount configuration (YAML)
- State tracking
- Comprehensive tests
- Documentation
- S3 cost calculations
- Data transfer calculations
- Reserved Instance amortization
- Savings Plans support
- GCP pricing provider
- Azure pricing provider
- Multi-cloud cost comparison
- Cost forecasting
- Anomaly detection hooks
- Stable API
- Production-ready
- Comprehensive documentation
- Performance benchmarks
costflow is part of the ResearchComputing ecosystem:
- prism - Interactive cloud workspaces
- lens - Lab notebook environments
- atom - Cloud-native HPC platform
- budgetflow - Budget management (coming soon)
- alertflow - Alert engine (coming soon)
Apache License 2.0 - See LICENSE for details.
Built from patterns observed in:
- cloudworkstation - Budget tracking and cost optimization
- aws-ide - State-based cost calculation
Inspired by the needs of research computing practitioners worldwide.
- Documentation: https://costflow.researchcomput.ing
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Slack: ResearchComputing Slack #costflow
Status: 🚧 Active Development First Release: Q1 2026 Maintainer: @scttfrdmn