cloud_cost_tracker 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +16 -0
- data/.yardopts +1 -0
- data/Gemfile +4 -0
- data/Guardfile +10 -0
- data/LICENSE.TXT +10 -0
- data/README.md +123 -0
- data/Rakefile +15 -0
- data/billing issues.md +4 -0
- data/bin/cloud_cost_tracker +94 -0
- data/cloud_cost_tracker.gemspec +36 -0
- data/config/accounts.example.yml +45 -0
- data/config/billing/compute-aws-servers/us-east-on_demand.yml +44 -0
- data/config/billing/compute-aws-snapshots.yml +5 -0
- data/config/billing/compute-aws-volumes.yml +5 -0
- data/config/billing/elasticache-aws.yml +15 -0
- data/config/billing/rds-aws-servers/us-east-on_demand-mysql.yml +26 -0
- data/config/billing/rds-aws-servers/us-east-on_demand-storage.yml +10 -0
- data/config/billing/storage-aws-directories.yml +5 -0
- data/config/database.example.yml +18 -0
- data/db/migrate/20120118000000_create_billing_records.rb +22 -0
- data/db/migrate/20120119000000_create_billing_codes.rb +20 -0
- data/lib/cloud_cost_tracker/billing/account_billing_policy.rb +106 -0
- data/lib/cloud_cost_tracker/billing/compute/aws/servers.rb +30 -0
- data/lib/cloud_cost_tracker/billing/compute/aws/snapshots.rb +43 -0
- data/lib/cloud_cost_tracker/billing/compute/aws/volumes.rb +30 -0
- data/lib/cloud_cost_tracker/billing/elasticache/clusters.rb +33 -0
- data/lib/cloud_cost_tracker/billing/rds/server_storage.rb +30 -0
- data/lib/cloud_cost_tracker/billing/rds/servers.rb +31 -0
- data/lib/cloud_cost_tracker/billing/resource_billing_policy.rb +119 -0
- data/lib/cloud_cost_tracker/billing/storage/aws/directories.rb +47 -0
- data/lib/cloud_cost_tracker/coding/account_coding_policy.rb +73 -0
- data/lib/cloud_cost_tracker/coding/compute/aws/account_coding_policy.rb +38 -0
- data/lib/cloud_cost_tracker/coding/compute/aws/servers.rb +26 -0
- data/lib/cloud_cost_tracker/coding/compute/aws/volumes.rb +26 -0
- data/lib/cloud_cost_tracker/coding/rds/account_coding_policy.rb +25 -0
- data/lib/cloud_cost_tracker/coding/rds/servers.rb +59 -0
- data/lib/cloud_cost_tracker/coding/resource_coding_policy.rb +25 -0
- data/lib/cloud_cost_tracker/extensions/fog_model.rb +41 -0
- data/lib/cloud_cost_tracker/models/billing_code.rb +14 -0
- data/lib/cloud_cost_tracker/models/billing_record.rb +88 -0
- data/lib/cloud_cost_tracker/tasks.rb +3 -0
- data/lib/cloud_cost_tracker/tracker.rb +86 -0
- data/lib/cloud_cost_tracker/util/module_instrospection.rb +13 -0
- data/lib/cloud_cost_tracker/version.rb +3 -0
- data/lib/cloud_cost_tracker.rb +113 -0
- data/lib/tasks/database.rake +25 -0
- data/lib/tasks/rspec.rake +6 -0
- data/lib/tasks/track.rake +19 -0
- data/spec/lib/cloud_cost_tracker/billing/account_billing_policy_spec.rb +33 -0
- data/spec/lib/cloud_cost_tracker/billing/resource_billing_policy_spec.rb +102 -0
- data/spec/lib/cloud_cost_tracker/cloud_cost_tracker_spec.rb +57 -0
- data/spec/lib/cloud_cost_tracker/coding/account_coding_policy_spec.rb +32 -0
- data/spec/lib/cloud_cost_tracker/coding/resource_coding_policy_spec.rb +23 -0
- data/spec/lib/cloud_cost_tracker/models/billing_code_spec.rb +35 -0
- data/spec/lib/cloud_cost_tracker/models/billing_record_spec.rb +206 -0
- data/spec/lib/cloud_cost_tracker/tracker_spec.rb +37 -0
- data/spec/spec_helper.rb +58 -0
- data/spec/support/_configure_logging.rb +6 -0
- data/writing-billing-policies.md +8 -0
- data/writing-coding-policies.md +7 -0
- metadata +224 -0
data/.gitignore
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--no-private --protected lib/**/*.rb - LICENSE.TXT
|
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'rspec',
|
5
|
+
:version => 2,
|
6
|
+
:cli => "--color --format documentation -r ./spec/spec_helper.rb" do
|
7
|
+
watch(%r{^spec/.+_spec\.rb$})
|
8
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
9
|
+
watch('spec/spec_helper.rb') { "spec" }
|
10
|
+
end
|
data/LICENSE.TXT
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
----------------
|
2
|
+
*MIT License*
|
3
|
+
|
4
|
+
Copyright (c) 2012 Benton Roberts
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
7
|
+
|
8
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
9
|
+
|
10
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
Cloud Cost Tracker
|
2
|
+
================
|
3
|
+
Records expenses for cloud computing resources in an ActiveRecord database.
|
4
|
+
|
5
|
+
*BETA VERSION - supports EC2 servers, EBS volumes, S3 buckets, and RDS servers*
|
6
|
+
|
7
|
+
|
8
|
+
----------------
|
9
|
+
What is it?
|
10
|
+
----------------
|
11
|
+
The Cloud Cost Tracker periodically polls one or more cloud computing accounts and determines the state of their associated cloud computing "resources": compute instances, disk volumes, stored objects, and so on. Each time an account is polled, a set of ActiveRecord objects ({CloudCostTracker::Billing::BillingRecord}s) is created or updated for each resource, containing the cost for that resource over the period of the BillingRecord.
|
12
|
+
|
13
|
+
The software can also attach "billing codes" to any resource, and to its corresponding billing records. These are arbitrary pairs of Strings which can be used to track resources for internal billing. By default, any AWS tags associated with a resource are translated into billing codes, but a framework is also provided for easily writing custom resource coding policies.
|
14
|
+
|
15
|
+
|
16
|
+
----------------
|
17
|
+
Why is it?
|
18
|
+
----------------
|
19
|
+
The Cloud Cost Tracker was created in response to the fact that Amazon Web Services does not provide per-resource billing information, which makes *internal* billing very difficult for organizations that make extensive use of AWS. Amazon's proposed solution -- creating, deleting, and cross-authorizing different AWS accounts for each internal billing entity -- is often not workable for organizations that need to perform these operations frequently. As a solution, this gem watches all resources across many accounts, and provides a way to define custom policies for assigning internal billing codes to all recorded expenses.
|
20
|
+
|
21
|
+
The Cloud Cost Tracker is intended to be a foundation library, on top of which more complex cloud billing / accounting applications can be built. Custom billing policies for cloud services and resources that are not yet implemented are simple to create, and are discovered automatically. Here are some currently-included default policies:
|
22
|
+
|
23
|
+
* EC2 server instance runtime costs:
|
24
|
+
{CloudCostTracker::Billing::Compute::AWS::ServerBillingPolicy}
|
25
|
+
* EBS volume storage costs:
|
26
|
+
{CloudCostTracker::Billing::Compute::AWS::VolumeBillingPolicy}
|
27
|
+
* S3 bucket storage costs:
|
28
|
+
{CloudCostTracker::Billing::Storage::AWS::DirectoryBillingPolicy}
|
29
|
+
* RDS server runtime costs:
|
30
|
+
{CloudCostTracker::Billing::AWS::RDS::ServerBillingPolicy}
|
31
|
+
* RDS server storage costs:
|
32
|
+
{CloudCostTracker::Billing::AWS::RDS::ServerStorageBillingPolicy}
|
33
|
+
|
34
|
+
|
35
|
+
----------------
|
36
|
+
Installation
|
37
|
+
----------------
|
38
|
+
Install the Cloud Cost Tracker gem, or list it in your `Gemfile`.
|
39
|
+
|
40
|
+
gem install cloud_cost_tracker [mysql2]
|
41
|
+
|
42
|
+
|
43
|
+
----------------
|
44
|
+
Usage
|
45
|
+
----------------
|
46
|
+
1) Add the BillingRecords table into your database.
|
47
|
+
Put `require 'cloud_cost_tracker/tasks'` in your Rakefile, then run:
|
48
|
+
|
49
|
+
rake db:migrate:tracker
|
50
|
+
|
51
|
+
2) In your Ruby app, `require` the gem, and set up an ActiveRecord connection.
|
52
|
+
In Rails, the connection is set up for you automatically on startup,
|
53
|
+
but here's an example for a non-Rails app:
|
54
|
+
|
55
|
+
require 'cloud_cost_tracker'
|
56
|
+
ActiveRecord::Base.establish_connection({
|
57
|
+
:adapter => 'mysql2', :database => 'cloud_cost_tracker',
|
58
|
+
:pool => 6 # reserve at least one connection per tracked account
|
59
|
+
})
|
60
|
+
|
61
|
+
3) Track all accounts loaded from a YAML file.
|
62
|
+
|
63
|
+
tracker = CloudCostTracker::Tracker.new(
|
64
|
+
YAML::load(File.read 'accounts.yml'), :logger => Logger.new(STDOUT)
|
65
|
+
).start
|
66
|
+
|
67
|
+
Here are the contents of the example file: `config/accounts.example.yml`:
|
68
|
+
|
69
|
+
AWS EC2 production account: # The account name - can be anything
|
70
|
+
:provider: AWS # This is the Fog provider Module
|
71
|
+
:service: Compute # Fog service Module. So, EC2 = Fog::Compute::AWS
|
72
|
+
:credentials:
|
73
|
+
:aws_access_key_id: XXXXXXXXXXXXXXXXXXXX
|
74
|
+
:aws_secret_access_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
75
|
+
:delay: 120 # Wait time between successive pollings (in seconds)
|
76
|
+
:exclude_resources:
|
77
|
+
- :account # No need to poll for accounts - those are listed here
|
78
|
+
- :flavors # You may or may not want EC2 server types
|
79
|
+
- :images # Takes a while to list all AMIs (works though)
|
80
|
+
AWS S3 development account:
|
81
|
+
:provider: AWS # S3 = Fog::Storage::AWS
|
82
|
+
:service: Storage
|
83
|
+
:credentials:
|
84
|
+
:aws_access_key_id: XXXXXXXXXXXXXXXXXXXX
|
85
|
+
:aws_secret_access_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
86
|
+
:delay: 150
|
87
|
+
:exclude_resources: # Pricing by Bucket is supported, but not by Object
|
88
|
+
- :files # This is required for S3 - objects can't be polled directly
|
89
|
+
|
90
|
+
The tracker will run asynchronously, with one thread per account.
|
91
|
+
Once the first account has been polled, and its resources coded,
|
92
|
+
{CloudCostTracker::Billing::BillingRecord}s will be generated for
|
93
|
+
each billable resource, and available through ActiveRecord.
|
94
|
+
|
95
|
+
|
96
|
+
----------------
|
97
|
+
Development
|
98
|
+
----------------
|
99
|
+
This project is still in its early stages, but most of the framework is in place.
|
100
|
+
More resource costs need to be modeled as
|
101
|
+
{CloudCostTracker::Billing::ResourceBillingPolicy} classes, but the patterns for
|
102
|
+
doing so are now laid out. For the details, see {file:writing-billing-policies.md},
|
103
|
+
{file:writing-coding-policies.md}, and the API documentation.
|
104
|
+
|
105
|
+
Helping hands are appreciated!
|
106
|
+
|
107
|
+
|
108
|
+
----------------
|
109
|
+
*Getting started with development*
|
110
|
+
|
111
|
+
1) Install project dependencies
|
112
|
+
|
113
|
+
gem install rake bundler
|
114
|
+
|
115
|
+
2) Fetch the project code
|
116
|
+
|
117
|
+
git clone https://github.com/benton/cloud_cost_tracker.git
|
118
|
+
cd cloud_cost_tracker
|
119
|
+
|
120
|
+
3) Bundle up and run the tests
|
121
|
+
|
122
|
+
bundle && rake
|
123
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# Set up bundler
|
2
|
+
%w{rubygems bundler bundler/gem_tasks}.each {|dep| require dep}
|
3
|
+
bundles = [:default]
|
4
|
+
Bundler.setup(:default)
|
5
|
+
require './lib/cloud_cost_tracker'
|
6
|
+
case CloudCostTracker.env
|
7
|
+
when 'development' then Bundler.setup(:default, :development)
|
8
|
+
when 'test' then Bundler.setup(:default, :development, :test)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Load all tasks from 'lib/tasks'
|
12
|
+
Dir["#{File.dirname(__FILE__)}/lib/tasks/*.rake"].sort.each {|ext| load ext}
|
13
|
+
|
14
|
+
desc 'Default: run specs.'
|
15
|
+
task :default => :spec
|
data/billing issues.md
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# == Synopsis
|
4
|
+
# Uses the Fog gem to generate billing records for cloud computing resources
|
5
|
+
#
|
6
|
+
# == Usage
|
7
|
+
# cloud_cost_tracker.rb [options] DB_CONFIG_FILE.YML ACCOUNTS_CONFIG_FILE.YML
|
8
|
+
#
|
9
|
+
# == Options (all options can be put into the config file)
|
10
|
+
# -d, --delay [INTEGER] Seconds between status updates. default = 180
|
11
|
+
# -l, --log-level [LEVEL] Sets Log4r level for console output. default = INFO
|
12
|
+
# -m, --migrate Update database schema
|
13
|
+
# -h, --help Displays help message
|
14
|
+
#
|
15
|
+
# == Author
|
16
|
+
# Benton Roberts
|
17
|
+
|
18
|
+
require 'optparse'
|
19
|
+
require 'cloud_cost_tracker'
|
20
|
+
LOG_LVLS = {
|
21
|
+
"DEBUG" => ::Logger::DEBUG,
|
22
|
+
"INFO" => ::Logger::INFO,
|
23
|
+
"WARN" => ::Logger::WARN,
|
24
|
+
"ERROR" => ::Logger::ERROR,
|
25
|
+
"FATAL" => ::Logger::FATAL
|
26
|
+
}
|
27
|
+
|
28
|
+
module CloudCostTracker
|
29
|
+
class CloudCostTrackerConsoleApp
|
30
|
+
|
31
|
+
def initialize
|
32
|
+
@log = FogTracker.default_logger(STDOUT)
|
33
|
+
parse_options
|
34
|
+
@log.info "Loading database configuration from #{ARGV[0]}"
|
35
|
+
config = YAML::load(File.open(ARGV[0]))[CloudCostTracker.env]
|
36
|
+
puts "Using database #{config['database']}..."
|
37
|
+
ActiveRecord::Base.establish_connection(config)
|
38
|
+
migrate if @opts[:migrate]
|
39
|
+
@log.info "Loading account information from #{@account_file}"
|
40
|
+
@accounts = YAML::load(File.open(@account_file))
|
41
|
+
@tracker = CloudCostTracker::Tracker.new(
|
42
|
+
@accounts, {:logger => @log, :delay => @opts[:delay]}
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
def go
|
47
|
+
@tracker.start
|
48
|
+
while true do
|
49
|
+
sleep 30 # Loop forever
|
50
|
+
#@log.info "Total resources = #{@tracker['*::*::*::*'].count}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def parse_options
|
55
|
+
@opts = {:log_level => 'INFO'}
|
56
|
+
optparse = OptionParser.new do |opts|
|
57
|
+
opts.banner = "Usage: tracker [options] DB_CONFIG.YML [ACCOUNTS.YML]"
|
58
|
+
opts.on('-d', '--delay SECONDS', Integer,
|
59
|
+
'Number of seconds between status updates') do |delay|
|
60
|
+
@opts[:delay] = delay
|
61
|
+
end
|
62
|
+
opts.on('-l', '--log-level LEVEL', 'Set logging level') do |log_level|
|
63
|
+
@opts[:log_level] = log_level.upcase
|
64
|
+
end
|
65
|
+
opts.on('-m', '--migrate', 'Update database schema') do
|
66
|
+
@opts[:migrate] = true
|
67
|
+
end
|
68
|
+
opts.on('-h', '--help', 'Display this help message') do
|
69
|
+
puts opts ; exit
|
70
|
+
end
|
71
|
+
end
|
72
|
+
optparse.parse!
|
73
|
+
@log.level = LOG_LVLS[@opts[:log_level]] if LOG_LVLS[@opts[:log_level]]
|
74
|
+
if ARGV.count < 1
|
75
|
+
@log.error "A database config file must be specified"
|
76
|
+
exit 1
|
77
|
+
end
|
78
|
+
if ARGV.count < 2
|
79
|
+
@account_file = "./config/accounts.yml"
|
80
|
+
else
|
81
|
+
@account_file = ARGV[1]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def migrate
|
86
|
+
@log.info "Updating database schema..."
|
87
|
+
migration_dir = File.expand_path('../../db/migrate', __FILE__)
|
88
|
+
ActiveRecord::Migrator.migrate migration_dir
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
CloudCostTracker::CloudCostTrackerConsoleApp.new.go
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "cloud_cost_tracker/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "cloud_cost_tracker"
|
7
|
+
s.version = CloudCostTracker::VERSION
|
8
|
+
s.authors = ["Benton Roberts"]
|
9
|
+
s.email = ["benton@bentonroberts.com"]
|
10
|
+
s.homepage = "http://github.com/benton/cloud_cost_tracker"
|
11
|
+
s.summary = %q{Records expenses for cloud computing resources }+
|
12
|
+
%q{in an ActiveRecord database}
|
13
|
+
s.description = %q{Periodically polls one or more cloud computing accounts }+
|
14
|
+
%q{using the fog gem, and generates ActiveRecord rows }+
|
15
|
+
%q{representing BillingRecords for each discovered resource.}
|
16
|
+
|
17
|
+
s.rubyforge_project = "cloud_cost_tracker"
|
18
|
+
|
19
|
+
s.files = `git ls-files`.split("\n")
|
20
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
21
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
22
|
+
s.require_paths = ["lib"]
|
23
|
+
|
24
|
+
# Runtime dependencies
|
25
|
+
s.add_dependency "fog_tracker", '>=0.3.9'
|
26
|
+
s.add_dependency "activerecord", '>=3'
|
27
|
+
|
28
|
+
# Development / Test dependencies
|
29
|
+
s.add_development_dependency "rake"
|
30
|
+
s.add_development_dependency "rspec"
|
31
|
+
s.add_development_dependency "yard"
|
32
|
+
s.add_development_dependency "guard"
|
33
|
+
s.add_development_dependency "guard-rspec"
|
34
|
+
s.add_development_dependency "ruby_gntp"
|
35
|
+
s.add_development_dependency "sqlite3"
|
36
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
AWS EC2 production account:
|
2
|
+
:provider: AWS
|
3
|
+
:service: Compute
|
4
|
+
:credentials:
|
5
|
+
:aws_access_key_id: XXXXXXXXXXXXXXXXXXXX
|
6
|
+
:aws_secret_access_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
7
|
+
:delay: 120
|
8
|
+
:exclude_resources:
|
9
|
+
AWS S3 account:
|
10
|
+
:provider: AWS
|
11
|
+
:service: Storage
|
12
|
+
:credentials:
|
13
|
+
:aws_access_key_id: XXXXXXXXXXXXXXXXXXXX
|
14
|
+
:aws_secret_access_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
15
|
+
:delay: 120
|
16
|
+
:exclude_resources:
|
17
|
+
# - :directories # (buckets)
|
18
|
+
- :files # secondary entity - does not work yet
|
19
|
+
AWS RDS account:
|
20
|
+
:provider: AWS
|
21
|
+
:service: RDS
|
22
|
+
:credentials:
|
23
|
+
:aws_access_key_id: XXXXXXXXXXXXXXXXXXXX
|
24
|
+
:aws_secret_access_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
25
|
+
:delay: 60
|
26
|
+
:exclude_resources:
|
27
|
+
- :parameters # secondary entity - does not work yet
|
28
|
+
AWS Route 53 account:
|
29
|
+
:provider: AWS
|
30
|
+
:service: DNS
|
31
|
+
:credentials:
|
32
|
+
:aws_access_key_id: XXXXXXXXXXXXXXXXXXXX
|
33
|
+
:aws_secret_access_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
34
|
+
:delay: 60
|
35
|
+
:exclude_resources:
|
36
|
+
- :records # secondary entity - does not work yet
|
37
|
+
AWS Elasticache account:
|
38
|
+
:provider: AWS
|
39
|
+
:service: Elasticache
|
40
|
+
:credentials:
|
41
|
+
:aws_access_key_id: XXXXXXXXXXXXXXXXXXXX
|
42
|
+
:aws_secret_access_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
43
|
+
:delay: 60
|
44
|
+
:exclude_resources:
|
45
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# Amazon EC2 On-demand instance pricing for the US-East region
|
2
|
+
# From http://aws.amazon.com/ec2/pricing/
|
3
|
+
---
|
4
|
+
unix:
|
5
|
+
# Standard On-Demand Instances
|
6
|
+
m1.small: 8.5
|
7
|
+
m1.medium: 16.0
|
8
|
+
m1.large: 32.0
|
9
|
+
m1.xlarge: 64.0
|
10
|
+
# Micro On-Demand Instances
|
11
|
+
t1.micro: 2.0
|
12
|
+
# Hi-Memory On-Demand Instances
|
13
|
+
m2.xlarge: 45.0
|
14
|
+
m2.2xlarge: 90.0
|
15
|
+
m2.4xlarge: 180.0
|
16
|
+
# Hi-CPU On-Demand Instances
|
17
|
+
c1.medium: 16.5
|
18
|
+
c1.xlarge: 66.0
|
19
|
+
# Cluster Compute Instances
|
20
|
+
cc1.4xlarge: 130.0
|
21
|
+
cc1.8xlarge: 240.0
|
22
|
+
# Cluster GPU Instances
|
23
|
+
cg1.4xlarge: 210.0
|
24
|
+
|
25
|
+
windows:
|
26
|
+
# Standard On-Demand Instances
|
27
|
+
m1.small: 11.5
|
28
|
+
m1.medium: 23.0
|
29
|
+
m1.large: 46.0
|
30
|
+
m1.xlarge: 92.0
|
31
|
+
# Micro On-Demand Instances
|
32
|
+
t1.micro: 2.0
|
33
|
+
# Hi-Memory On-Demand Instances
|
34
|
+
m2.xlarge: 57.0
|
35
|
+
m2.2xlarge: 114.0
|
36
|
+
m2.4xlarge: 228.0
|
37
|
+
# Hi-CPU On-Demand Instances
|
38
|
+
c1.medium: 28.5
|
39
|
+
c1.xlarge: 114.0
|
40
|
+
# Cluster Compute Instances
|
41
|
+
cc1.4xlarge: 161.0
|
42
|
+
cc1.8xlarge: 297.0
|
43
|
+
# Cluster GPU Instances
|
44
|
+
cg1.4xlarge: 260.0
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# Amazon Elasticache On-demand instance pricing by Region
|
2
|
+
# From http://aws.amazon.com/elasticache/pricing/
|
3
|
+
---
|
4
|
+
# These numbers are in cents per hour per node in the cluster
|
5
|
+
us-east:
|
6
|
+
# Standard On-Demand Instances
|
7
|
+
cache.m1.small: 9.0
|
8
|
+
cache.m1.large: 36.0
|
9
|
+
cache.m1.xlarge: 72.0
|
10
|
+
# Hi-Memory On-Demand Instances
|
11
|
+
cache.m2.xlarge: 50.5
|
12
|
+
cache.m2.2xlarge: 101.0
|
13
|
+
cache.m2.4xlarge: 202.0
|
14
|
+
# Hi-CPU On-Demand Instances
|
15
|
+
cache.c1.xlarge: 74.0
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Amazon RDS On-demand MySQL instance pricing for the US-East region
|
2
|
+
# From http://aws.amazon.com/rds/pricing/
|
3
|
+
---
|
4
|
+
# Standard Deployment
|
5
|
+
standard:
|
6
|
+
# DB Instance Class (On-Demand)
|
7
|
+
db.t1.micro: 2.5
|
8
|
+
db.m1.small: 10.5
|
9
|
+
db.m1.large: 41.5
|
10
|
+
db.m1.xlarge: 83.0
|
11
|
+
# High-Memory DB Instance Class
|
12
|
+
db.m2.xlarge: 58.5
|
13
|
+
db.m2.2xlarge: 117.0
|
14
|
+
db.m2.4xlarge: 234.0
|
15
|
+
|
16
|
+
# Multi-AZ Deployment
|
17
|
+
multi_az:
|
18
|
+
# DB Instance Class (On-Demand)
|
19
|
+
db.t1.micro: 5.0
|
20
|
+
db.m1.small: 21.0
|
21
|
+
db.m1.large: 83.0
|
22
|
+
db.m1.xlarge: 166.0
|
23
|
+
# High-Memory DB Instance Class
|
24
|
+
db.m2.xlarge: 117.0
|
25
|
+
db.m2.2xlarge: 234.0
|
26
|
+
db.m2.4xlarge: 468.0
|
@@ -0,0 +1,18 @@
|
|
1
|
+
development:
|
2
|
+
username: root
|
3
|
+
pool: 10 # Best to use at lease one connection per tracked account
|
4
|
+
timeout: 10000 # Leave this nice and long
|
5
|
+
adapter: sqlite3
|
6
|
+
database: db/cloud_cost_tracker_development.sqlite3
|
7
|
+
test:
|
8
|
+
username: root
|
9
|
+
pool: 10 # Best to use at lease one connection per tracked account
|
10
|
+
timeout: 10000 # Leave this nice and long
|
11
|
+
adapter: sqlite3
|
12
|
+
database: db/cloud_cost_tracker_test.sqlite3
|
13
|
+
production:
|
14
|
+
username: root
|
15
|
+
pool: 10 # Best to use at lease one connection per tracked account
|
16
|
+
timeout: 10000 # Leave this nice and long
|
17
|
+
adapter: sqlite3
|
18
|
+
database: db/cloud_cost_tracker_production.sqlite3
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class CreateBillingRecords < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :billing_records do |t|
|
4
|
+
t.string :provider
|
5
|
+
t.string :service
|
6
|
+
t.string :account
|
7
|
+
t.string :resource_id
|
8
|
+
t.string :billing_type
|
9
|
+
t.datetime :start_time
|
10
|
+
t.datetime :stop_time
|
11
|
+
t.decimal :cost_per_hour, :scale => 10, :precision => 20
|
12
|
+
t.decimal :total_cost, :scale => 10, :precision => 20
|
13
|
+
t.string :resource_type
|
14
|
+
|
15
|
+
t.timestamps
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.down
|
20
|
+
drop_table :billing_records
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class CreateBillingCodes < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :billing_codes do |t|
|
4
|
+
t.string :key, :size => 512, :null => false
|
5
|
+
t.string :value, :size => 512
|
6
|
+
end
|
7
|
+
|
8
|
+
create_table :billing_records_codes, :id => false do |t|
|
9
|
+
t.integer :billing_record_id, :null => false
|
10
|
+
t.integer :billing_code_id, :null => false
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.down
|
16
|
+
drop_table :billing_codes
|
17
|
+
drop_table :billing_records_codes
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module CloudCostTracker
|
2
|
+
module Billing
|
3
|
+
# Implements the logic for billing all resources in a single account.
|
4
|
+
# Initializes the necessary ResourceBillingPolicy objects, sorts the
|
5
|
+
# resources by policy, and calls get_cost_for_duration twice on each
|
6
|
+
# resource's policy to compute the charges.
|
7
|
+
class AccountBillingPolicy
|
8
|
+
|
9
|
+
# Creates an object that implements the default account billing policy
|
10
|
+
# @param [Array <Fog::Model>] resources the resources to bill for
|
11
|
+
# @param [Hash] options optional parameters:
|
12
|
+
# - :logger - a Ruby Logger-compatible object
|
13
|
+
def initialize(resources, options={})
|
14
|
+
@log = options[:logger] || FogTracker.default_logger
|
15
|
+
setup_resource_billing_agents(resources)
|
16
|
+
# Initialize data structures for caching costs
|
17
|
+
# the hourly cost for each resource, and billing agent
|
18
|
+
@hourly_cost = Hash.new # @hourly_cost[resource][agent]
|
19
|
+
# the total cost for each resource, and billing agent
|
20
|
+
@total_cost = Hash.new # @total_cost[resource][agent]
|
21
|
+
resources.each do |resource|
|
22
|
+
@hourly_cost[resource] ||= Hash.new
|
23
|
+
@total_cost[resource] ||= Hash.new
|
24
|
+
end
|
25
|
+
@account = resources.first.tracker_account # Save account info
|
26
|
+
end
|
27
|
+
|
28
|
+
# An initializer called by the framework once per billing cycle.
|
29
|
+
# Override this method if you need to perform high-latency operations,
|
30
|
+
# like network transactions, that should not be performed per-resource.
|
31
|
+
def setup(resources) ; end
|
32
|
+
|
33
|
+
# Defines the default method for billing all resources
|
34
|
+
def bill_for(resources)
|
35
|
+
return if resources.empty?
|
36
|
+
setup_resource_billing_agents(resources)
|
37
|
+
delay = @account[:delay].to_i
|
38
|
+
bill_end = Time.now
|
39
|
+
bill_start = @account[:preceeding_update_time] || (bill_end - delay)
|
40
|
+
bill_duration = bill_end - bill_start
|
41
|
+
# calculate the hourly and total cost for all resources, by type
|
42
|
+
@agents.keys.each do |resource_class|
|
43
|
+
collection = resources.select {|r| r.class == resource_class}
|
44
|
+
collection_name = collection.first.collection.class.name.split('::').last
|
45
|
+
@log.info "Computing costs for #{collection.size}"+
|
46
|
+
" #{collection_name} in account #{@account[:name]}"
|
47
|
+
collection.each do |resource|
|
48
|
+
@log.debug "Computing costs for #{resource.tracker_description}"+
|
49
|
+
" in account #{@account[:name]}"
|
50
|
+
@agents[resource.class].each do |billing_agent|
|
51
|
+
@total_cost[resource][billing_agent] = billing_agent.
|
52
|
+
get_cost_for_duration(resource, bill_duration).round(PRECISION)
|
53
|
+
@hourly_cost[resource][billing_agent] = billing_agent.
|
54
|
+
get_cost_for_duration(resource, SECONDS_PER_HOUR).round(PRECISION)
|
55
|
+
@log.debug "Computed costs for #{resource.tracker_description}"+
|
56
|
+
" in account #{@account[:name]}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
@log.info "Computed costs for #{collection.size}"+
|
60
|
+
" #{collection_name} in account #{@account[:name]}"
|
61
|
+
end
|
62
|
+
write_records_for(resources, bill_start, bill_end)
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# Writes the billing records - @total_cost and @total_cost must be populated
|
68
|
+
# @param [Array <Fog::Model>] resources the resources to write records for
|
69
|
+
# @param [Time] start_time the start time for any new BillingRecords
|
70
|
+
def write_records_for(resources, start_time, end_time)
|
71
|
+
#ActiveRecord::Base.connection_pool.with_connection do
|
72
|
+
# Write BillingRecords for all resources, by type
|
73
|
+
@agents.keys.each do |resource_class|
|
74
|
+
collection = resources.select {|r| r.class == resource_class}
|
75
|
+
collection_name = collection.first.collection.class.name.split('::').last
|
76
|
+
@log.info "Writing billing records for #{collection.size} "+
|
77
|
+
"#{collection_name} in account #{@account[:name]}"
|
78
|
+
collection.each do |resource|
|
79
|
+
@agents[resource.class].each do |billing_agent|
|
80
|
+
billing_agent.write_billing_record_for(resource,
|
81
|
+
@hourly_cost[resource][billing_agent],
|
82
|
+
@total_cost[resource][billing_agent],
|
83
|
+
start_time, end_time
|
84
|
+
)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
@log.info "Wrote billing records for #{collection.size} "+
|
88
|
+
"#{collection_name} in account #{@account[:name]}"
|
89
|
+
end
|
90
|
+
#end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Builds a Hash of BillingPolicy agents, indexed by resource Class name
|
94
|
+
def setup_resource_billing_agents(resources)
|
95
|
+
@agents = Hash.new
|
96
|
+
((resources.collect {|r| r.class}).uniq).each do |resource_class|
|
97
|
+
@agents[resource_class] = CloudCostTracker::create_billing_agents(
|
98
|
+
resource_class, {:logger => @log})
|
99
|
+
@agents[resource_class].each {|agent| agent.setup(resources)}
|
100
|
+
@agents.delete(resource_class) if @agents[resource_class].empty?
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|