Skip to content

Commit

Permalink
Merge pull request #124 from serpentblade/master
Browse files Browse the repository at this point in the history
Add Reserved Instance Pricing Support
  • Loading branch information
powdahound committed Aug 25, 2015
2 parents 1fd7e0d + 7b7d4e6 commit b50d689
Show file tree
Hide file tree
Showing 5 changed files with 17,575 additions and 1,899 deletions.
49 changes: 42 additions & 7 deletions in/index.html.mako
Expand Up @@ -69,6 +69,21 @@
</ul>
</div>

<div class="btn-group" id='reserved-term-dropdown'>
<a class="btn dropdown-toggle btn-primary" data-toggle="dropdown" href="#">
<i class="icon-globe icon-white"></i>
Reserved: <span class="text">1 yr - No Upfront</span>
<span class="caret"></span>
</a>
<ul class="dropdown-menu" role="menu">
<li><a href="javascript:;" data-reserved-term='yrTerm1.noUpfront'>1 yr - No Upfront</a></li>
<li><a href="javascript:;" data-reserved-term='yrTerm1.partialUpfront'>1 yr - Partial Upfront</a></li>
<li><a href="javascript:;" data-reserved-term='yrTerm1.allUpfront'>1 yr - Full Upfront</a></li>
<li><a href="javascript:;" data-reserved-term='yrTerm3.partialUpfront'>3 yr - Partial Upfront</a></li>
<li><a href="javascript:;" data-reserved-term='yrTerm3.allUpfront'>3 yr - Full Upfront</a></li>
</ul>
</div>

<div class="btn-group" id="filter-dropdown">
<a class="btn dropdown-toggle btn-primary" data-toggle="dropdown" href="#">
<i class="icon-filter icon-white"></i>
Expand Down Expand Up @@ -136,10 +151,23 @@
</th>
<th class="enhanced-networking">Enhanced Networking</th>
<th class="linux-virtualization">Linux Virtualization</th>
<th class="cost">Linux cost</th>
<th class="cost-mswin">Windows cost</th>
<th class="cost-mswinSQLWeb">Windows SQL Web cost</th>
<th class="cost-mswinSQL">Windows SQL Std cost</th>

<th class="cost-ondemand-linux">Linux On Demand cost</th>
<th class="cost-reserved-linux">
<abbr title='Reserved costs are an "effective" hourly rate, calculated by hourly rate + (upfront cost / hours in reserved term). Actual hourly rates may vary.'>Linux Reserved cost</abbr>
</th>
<th class="cost-ondemand-mswin">Windows On Demand cost</th>
<th class="cost-reserved-mswin">
<abbr title='Reserved costs are an "effective" hourly rate, calculated by hourly rate + (upfront cost / hours in reserved term). Actual hourly rates may vary.'>Windows Reserved cost</abbr>
</th>
<th class="cost-ondemand-mswinSQLWeb">Windows SQL Web On Demand cost</th>
<th class="cost-reserved-mswinSQLWeb">
<abbr title='Reserved costs are an "effective" hourly rate, calculated by hourly rate + (upfront cost / hours in reserved term). Actual hourly rates may vary.'>Windows SQL Web Reserved cost</abbr>
</th>
<th class="cost-ondemand-mswinSQL">Windows SQL Std On Demand cost</th>
<th class="cost-reserved-mswinSQL">
<abbr title='Reserved costs are an "effective" hourly rate, calculated by hourly rate + (upfront cost / hours in reserved term). Actual hourly rates may vary.'>Windows SQL Std Reserved cost</abbr>
</th>
</tr>
</thead>
<tbody>
Expand Down Expand Up @@ -226,13 +254,20 @@
% endif
</td>
% for platform in ['linux', 'mswin', 'mswinSQLWeb', 'mswinSQL']:
<td class="cost cost-${platform}" data-pricing='${json.dumps({r:p.get(platform, p.get('os',0)) for r,p in inst['pricing'].iteritems()}) | h}'>
% if inst['pricing'].get('us-east-1', {}).get(platform, 'N/A') != "N/A":
$${inst['pricing']['us-east-1'][platform]} per hour
<td class="cost-ondemand cost-ondemand-${platform}" data-pricing='${json.dumps({r:p.get(platform, p.get('os',{})).get('ondemand') for r,p in inst['pricing'].iteritems()}) | h}'>
% if inst['pricing'].get('us-east-1', {}).get(platform, {}).get('ondemand', 'N/A') != "N/A":
$${inst['pricing']['us-east-1'][platform]['ondemand']} per hour
% else:
unavailable
% endif
</td>
<td class="cost-reserved cost-reserved-${platform}" data-pricing='${json.dumps({r:p.get(platform, p.get('os',{})).get('reserved', {}) for r,p in inst['pricing'].iteritems()}) | h}'>
% if inst['pricing'].get('us-east-1', {}).get(platform, {}).get('reserved', 'N/A') != "N/A":
$${inst['pricing']['us-east-1'][platform]['reserved']['yrTerm1.noUpfront']} per hour
% else:
unavailable
% endif
</td>
% endfor
</tr>
% endfor
Expand Down
90 changes: 77 additions & 13 deletions scrape.py
Expand Up @@ -185,7 +185,13 @@ def transform_region(reg):
return base + "-" + num


def add_pricing(imap, data, platform):
def add_pricing(imap, data, platform, pricing_mode):
if pricing_mode == 'od':
add_ondemand_pricing(imap, data, platform)
elif pricing_mode == 'ri':
add_reserved_pricing(imap, data, platform)

def add_ondemand_pricing(imap, data, platform):
for region_spec in data['config']['regions']:
region = transform_region(region_spec['region'])
for t_spec in region_spec['instanceTypes']:
Expand All @@ -200,8 +206,10 @@ def add_pricing(imap, data, platform):
inst = imap[i_type]
inst.pricing.setdefault(region, {})
# print "%s/%s" % (region, i_type)

inst.pricing[region].setdefault(platform, {})
for col in i_spec['valueColumns']:
inst.pricing[region][platform] = col['prices']['USD']
inst.pricing[region][platform]['ondemand'] = col['prices']['USD']

# ECU is only available here
ecu = i_spec['ECU']
Expand All @@ -210,26 +218,82 @@ def add_pricing(imap, data, platform):
else:
inst.ECU = float(ecu)

def add_reserved_pricing(imap, data, platform):
for region_spec in data['config']['regions']:
region = transform_region(region_spec['region'])
for t_spec in region_spec['instanceTypes']:
i_type = t_spec['type']
# As best I can tell, this type doesn't exist, but is
# in the pricing charts anyways.
if i_type == 'cc2.4xlarge':
continue
assert i_type in imap, "Unknown instance size: %s" % (i_type, )
inst = imap[i_type]
inst.pricing.setdefault(region, {})
# print "%s/%s" % (region, i_type)
inst.pricing[region].setdefault(platform, {})
inst.pricing[region][platform].setdefault('reserved', {})

termPricing = {}

for term in t_spec['terms']:
for po in term['purchaseOptions']:
for value in po['valueColumns']:
if value['name'] == 'effectiveHourly':
termPricing[term['term'] + '.' + po['purchaseOption']] = value['prices']['USD']

inst.pricing[region][platform]['reserved'] = termPricing


def add_pricing_info(instances):

pricing_modes = ['ri', 'od']

reserved_name_map = {
'linux': 'linux-unix-shared',
'mswin': 'windows-shared',
'mswinSQL': 'windows-with-sql-server-standard-shared',
'mswinSQLWeb': 'windows-with-sql-server-web-shared'
}

for i in instances:
i.pricing = {}

by_type = {i.instance_type: i for i in instances}



for platform in ['linux', 'mswin', 'mswinSQL', 'mswinSQLWeb']:
# current generation
pricing_url = 'http://aws.amazon.com/ec2/pricing/json/%s-od.json' % (platform,)
pricing = json.loads(urllib2.urlopen(pricing_url).read())
add_pricing(by_type, pricing, platform)

# previous generation
pricing_url = 'http://a0.awsstatic.com/pricing/1/ec2/previous-generation/%s-od.min.js' % (platform,)
jsonp_string = urllib2.urlopen(pricing_url).read()
json_string = re.sub(r"(\w+):", r'"\1":', jsonp_string[jsonp_string.index('callback(') + 9 : -2]) # convert into valid json
for pricing_mode in pricing_modes:
# current generation
if pricing_mode == 'od':
pricing_url = 'https://a0.awsstatic.com/pricing/1/deprecated/ec2/%s-od.json' % (platform,)
else:
pricing_url = 'http://a0.awsstatic.com/pricing/1/ec2/ri-v2/%s.min.js' % (reserved_name_map[platform],)


pricing = fetch_data(pricing_url)
add_pricing(by_type, pricing, platform, pricing_mode)

# previous generation
if pricing_mode == 'od':
pricing_url = 'http://a0.awsstatic.com/pricing/1/ec2/previous-generation/%s-od.min.js' % (platform,)
else:
pricing_url = 'http://a0.awsstatic.com/pricing/1/ec2/previous-generation/ri-v2/%s.min.js' % (reserved_name_map[platform],)

pricing = fetch_data(pricing_url)
add_pricing(by_type, pricing, platform, pricing_mode)

def fetch_data(url):
content = urllib2.urlopen(url).read()
try:
pricing = json.loads(content)
except ValueError:
# if the data isn't compatiable JSON, try to parse as jsonP
json_string = re.sub(r"(\w+):", r'"\1":', content[content.index('callback(') + 9 : -2]) # convert into valid json
pricing = json.loads(json_string)
add_pricing(by_type, pricing, platform)


return pricing
def add_eni_info(instances):
eni_url = "http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-eni.html"
tree = etree.parse(urllib2.urlopen(eni_url), etree.HTMLParser())
Expand Down
44 changes: 41 additions & 3 deletions www/default.js
@@ -1,5 +1,6 @@
var current_cost_duration = 'hourly';
var current_region = 'us-east-1';
var current_reserved_term = 'yrTerm1.noUpfront';
var data_table = null;

var require = Array();
Expand All @@ -22,7 +23,7 @@ function init_data_table() {
"sType": "span-sort"
},
{
"aTargets": ["ecu-per-core", "enhanced-networking", "maxips", "linux-virtualization", "cost-mswinSQLWeb", "cost-mswinSQL", "ebs-throughput", "ebs-iops", "max_bandwidth"],
"aTargets": ["ecu-per-core", "enhanced-networking", "maxips", "linux-virtualization", "cost-ondemand-mswinSQLWeb", "cost-ondemand-mswinSQL", "cost-reserved-mswinSQLWeb", "cost-reserved-mswinSQL", "ebs-throughput", "ebs-iops", "max_bandwidth"],
"bVisible": false
}
],
Expand Down Expand Up @@ -78,12 +79,12 @@ function change_cost(duration) {
"hourly": 1,
"daily": 24,
"weekly": (7*24),
"monthly": (24*30),
"monthly": (30*24),
"annually": (365*24)
};
var multiplier = hour_multipliers[duration];
var per_time;
$.each($("td.cost"), function(i, elem) {
$.each($("td.cost-ondemand"), function(i, elem) {
elem = $(elem);
per_time = elem.data("pricing")[current_region];
if (per_time && !isNaN(per_time)) {
Expand All @@ -94,6 +95,25 @@ function change_cost(duration) {
}
});

$.each($("td.cost-reserved"), function(i, elem) {
elem = $(elem);
per_time = elem.data("pricing")[current_region];

if(!per_time) {
elem.text("unavailable");
return;
}

per_time = per_time[current_reserved_term];

if (per_time && !isNaN(per_time)) {
per_time = (per_time * multiplier).toFixed(3);
elem.text("$" + per_time + " " + duration);
} else {
elem.text("unavailable");
}
});

current_cost_duration = duration;
maybe_update_url();
}
Expand All @@ -114,6 +134,19 @@ function change_region(region) {
change_cost(current_cost_duration);
}

function change_reserved_term(term) {
current_reserved_term = term;
var $dropdown = $('#reserved-term-dropdown'),
$activeLink = $dropdown.find('li a[data-reserved-term="'+term+'"]'),
term_name = $activeLink.text();

$dropdown.find('li').removeClass('active');
$activeLink.closest('li').addClass('active');

$dropdown.find('.dropdown-toggle .text').text(term_name);
change_cost(current_cost_duration);
}

// Update all visible costs to the current duration.
// Called after new columns or rows are shown as their costs may be inaccurate.
function redraw_costs() {
Expand Down Expand Up @@ -250,6 +283,7 @@ function on_data_table_initialized() {

change_region('us-east-1');
change_cost('hourly');
change_reserved_term('yrTerm1.noUpfront');

$.extend($.fn.dataTableExt.oStdClasses, {
"sWrapper": "dataTables_wrapper form-inline"
Expand All @@ -271,6 +305,10 @@ function on_data_table_initialized() {
$("#region-dropdown li").bind("click", function(e) {
change_region($(e.target).data('region'));
});

$("#reserved-term-dropdown li").bind("click", function(e) {
change_reserved_term($(e.target).data('reservedTerm'));
});
}

// sorting for colums with more complex data
Expand Down

0 comments on commit b50d689

Please sign in to comment.