Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Reserved Instance Pricing Support #124

Merged
merged 3 commits into from Aug 25, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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