Skip to content

scttfrdmn/costflow

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

costflow

AWS cost calculation and tracking library for Go

CI Go Report Card GoDoc License

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.

Features

  • 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

Installation

go get github.com/researchcomputing/costflow

Quick Start

Basic Cost Calculation

package 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
}

With Institutional Discounts

// 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,
)

State-Based Tracking

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)

Discount Configuration

costflow supports complex discount scenarios via YAML configuration:

Example: discounts.yaml

# 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"

Discount Stacking

Discounts stack in this order:

  1. Base AWS rate (from Pricing API or static table)
  2. Global discount (e.g., EDP 15%)
  3. Service discount (e.g., EC2 PPA 20%)
  4. Instance family discount (e.g., Graviton +5%)
  5. Regional discount (e.g., us-west-2 +2%)
  6. Credits applied (promotional, research grants)
  7. 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

Architecture

Core Components

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

Interfaces

// 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)
}

Use Cases

Use Case 1: Interactive Cloud Workspaces (prism)

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)

Use Case 2: HPC Batch Jobs (atom)

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,
)

Use Case 3: Notebook Environments (lens)

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)

Pricing Data Sources

AWS Pricing API (Default)

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

Static Rate Tables

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,
        },
    },
})

Custom Overrides

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)

Testing

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=. ./...

Contributing

Contributions welcome! Please see CONTRIBUTING.md.

Development Setup

# 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

Roadmap

v0.1.0 (Current)

  • Core pricing provider interface
  • AWS Pricing API integration
  • Basic cost calculation (EC2, EBS)
  • Discount configuration (YAML)
  • State tracking
  • Comprehensive tests
  • Documentation

v0.2.0

  • S3 cost calculations
  • Data transfer calculations
  • Reserved Instance amortization
  • Savings Plans support
  • GCP pricing provider

v0.3.0

  • Azure pricing provider
  • Multi-cloud cost comparison
  • Cost forecasting
  • Anomaly detection hooks

v1.0.0

  • Stable API
  • Production-ready
  • Comprehensive documentation
  • Performance benchmarks

Related Projects

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)

License

Apache License 2.0 - See LICENSE for details.

Acknowledgments

Built from patterns observed in:

Inspired by the needs of research computing practitioners worldwide.

Support


Status: 🚧 Active Development First Release: Q1 2026 Maintainer: @scttfrdmn

About

AWS cost calculation and tracking library for Go - Part of ResearchComputing ecosystem

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages