mirror of
https://github.com/OpenNebula/one.git
synced 2025-02-23 21:57:43 +03:00
Feature #1098: Add accounting info to Self-Service dashboard
(cherry picked from commit 934ba8ae2edca081727ca2bc74c12e0eb5306c14)
This commit is contained in:
parent
048ddc6624
commit
e6dad7ab3e
@ -570,4 +570,59 @@ class OCCIServer < CloudServer
|
||||
|
||||
return vnc.proxy(vm)
|
||||
end
|
||||
|
||||
##########################################################################
|
||||
# Accounting
|
||||
##########################################################################
|
||||
|
||||
def get_user_accounting(options)
|
||||
tstart = options[:start].to_i
|
||||
tend = options[:end].to_i
|
||||
interval = options[:interval].to_i
|
||||
meters = options[:monitor_resources]
|
||||
|
||||
acct_options = {:start_time => tstart,
|
||||
:end_time => tend}
|
||||
info_flag = Pool::INFO_ALL
|
||||
|
||||
result = {}
|
||||
meters_a = meters.split(',')
|
||||
meters_a.each do | meter |
|
||||
result[meter] = []
|
||||
end
|
||||
|
||||
pool = VirtualMachinePool.new(@client)
|
||||
acct_xml = pool.accounting_xml(info_flag, acct_options)
|
||||
|
||||
if OpenNebula.is_error?(acct_xml)
|
||||
error = Error.new(acct_xml.message)
|
||||
return [500, error.to_json]
|
||||
end
|
||||
|
||||
xml = XMLElement.new
|
||||
xml.initialize_xml(acct_xml, 'HISTORY_RECORDS')
|
||||
|
||||
while tstart < tend
|
||||
|
||||
tstep = tstart + interval
|
||||
count = Hash.new
|
||||
|
||||
xml.each("HISTORY[STIME<=#{tstep} and ETIME=0 or STIME<=#{tstep} and ETIME>=#{tstart}]") do |hr|
|
||||
|
||||
meters_a.each do | meter |
|
||||
count[meter] ||= 0
|
||||
count[meter] += hr["VM/#{meter}"].to_i if hr["VM/#{meter}"]
|
||||
end
|
||||
end
|
||||
|
||||
count.each do | mname, mcount |
|
||||
result[mname] << [tstart, mcount]
|
||||
end
|
||||
|
||||
tstart = tstep
|
||||
end
|
||||
|
||||
return [200, {:monitoring => result}.to_json]
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -443,4 +443,8 @@ post '/ui/startvnc/:id' do
|
||||
@occi_server.startvnc(vm_id, settings.vnc)
|
||||
end
|
||||
|
||||
get '/ui/accounting' do
|
||||
@occi_server.get_user_accounting(params)
|
||||
end
|
||||
|
||||
Sinatra::Application.run!
|
||||
|
@ -133,7 +133,7 @@ $(document).ready(function () {
|
||||
applyDefaultStyles: false
|
||||
, center__paneSelector: ".outer-center"
|
||||
, west__paneSelector: ".outer-west"
|
||||
, west__size: 181
|
||||
, west__size: 200
|
||||
, north__size: 26
|
||||
, south__size: 26
|
||||
, spacing_open: 0 // ALL panes
|
||||
|
@ -255,22 +255,18 @@ var OCCI = {
|
||||
callback_error(request, OCCI.Error(response)) : null;
|
||||
}
|
||||
});
|
||||
}
|
||||
/*
|
||||
"monitor": function(params,resource,all){
|
||||
},
|
||||
|
||||
"accounting": function(params, resource){
|
||||
var callback = params.success;
|
||||
var callback_error = params.error;
|
||||
var data = params.data;
|
||||
|
||||
var method = "monitor";
|
||||
var action = OpenNebula.Helper.action(method);
|
||||
var request = OpenNebula.Helper.request(resource,method, data);
|
||||
|
||||
var url = resource.toLowerCase();
|
||||
url = all ? url + "/monitor" : url + "/" + params.data.id + "/monitor";
|
||||
var method = "accounting";
|
||||
var request = OCCI.Helper.request(resource, method, data);
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
url: 'ui/accounting',
|
||||
type: "GET",
|
||||
data: data['monitor'],
|
||||
dataType: "json",
|
||||
@ -279,11 +275,10 @@ var OCCI = {
|
||||
},
|
||||
error: function(response){
|
||||
return callback_error ?
|
||||
callback_error(request, OpenNebula.Error(response)) : null;
|
||||
callback_error(request, OCCI.Error(response)) : null;
|
||||
}
|
||||
});
|
||||
}
|
||||
*/
|
||||
},
|
||||
|
||||
"Auth": {
|
||||
@ -600,7 +595,9 @@ var OCCI = {
|
||||
},
|
||||
"show" : function(params){
|
||||
OCCI.Action.show(params,OCCI.User.resource);
|
||||
},
|
||||
"accounting" : function(params){
|
||||
OCCI.Action.accounting(params, OCCI.User.resource);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,17 @@
|
||||
/* limitations under the License. */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
var user_acct_graphs = [
|
||||
{ title : tr("CPU"),
|
||||
monitor_resources : "CPU",
|
||||
humanize_figures : false
|
||||
},
|
||||
{ title : tr("Memory"),
|
||||
monitor_resources : "MEMORY",
|
||||
humanize_figures : true
|
||||
}
|
||||
];
|
||||
|
||||
var dashboard_tab_content =
|
||||
'<table id="dashboard_table">\
|
||||
<tr>\
|
||||
@ -47,6 +58,41 @@ var dashboard_tab_content =
|
||||
<tr id="network_quotas"><td class="key_td">'+tr("Networks")+':</td>\
|
||||
<td class="value_td">'+$network_count+'</td></tr>\
|
||||
</table>\
|
||||
\
|
||||
</div>\
|
||||
</div>\
|
||||
</td>\
|
||||
</tr>\
|
||||
<tr>\
|
||||
<td>\
|
||||
<div class="panel" id="user_acct_panel">\
|
||||
<h3>' + tr("Usages") + '<i class="icon-refresh user_acct_date_ok" style="float:right;cursor:pointer"></i></h3>\
|
||||
<div class="panel_info">\
|
||||
\
|
||||
<div style="margin-left:20px;text-align:center;">\
|
||||
'+tr("From")+': <input type="text" style="font-size:12px;width: 80px;" id="user_acct_from" name="from"/> \
|
||||
'+tr("To")+': <input type="text" style="font-size:12px;width: 80px;" id="user_acct_to" name="to"/>\
|
||||
<a href="#" class="user_acct_date_ok">'+tr('Go!')+' <i class="icon-ok"></i></a>\
|
||||
</div>\
|
||||
\
|
||||
<table class="info_table">\
|
||||
<tr><td id="legend_CPU"></td></tr>\
|
||||
<tr><td style="border:0;width:800px!important;">\
|
||||
<div id="user_acct_CPU" style="height:100px;position:relative;left:0px;overflow: hidden;">'+
|
||||
spinner+
|
||||
'</div>\
|
||||
</td>\
|
||||
</tr>\
|
||||
</table>\
|
||||
<table class="info_table">\
|
||||
<tr><td id="legend_MEMORY"></td></tr>\
|
||||
<tr><td style="border:0;width:800px!important;">\
|
||||
<div id="user_acct_MEMORY" style="height:100px;position:relative;left:0px;overflow: hidden;">'+
|
||||
spinner+
|
||||
'</div>\
|
||||
</td>\
|
||||
</tr>\
|
||||
</table>\
|
||||
\
|
||||
</div>\
|
||||
</div>\
|
||||
@ -136,15 +182,18 @@ Sunstone.addAction('User.refresh', {
|
||||
}
|
||||
});
|
||||
|
||||
Sunstone.addAction('User.accounting', {
|
||||
type: "monitor",
|
||||
call: OCCI.User.accounting,
|
||||
callback: function(req, response){
|
||||
var info = req.request.data[0].monitor;
|
||||
plot_graph(response, '#user_acct_panel', 'user_acct_', info);
|
||||
},
|
||||
error: onError
|
||||
});
|
||||
|
||||
Sunstone.addMainTab('dashboard_tab',dashboard_tab);
|
||||
|
||||
function quickstart_setup(){
|
||||
|
||||
$('#dashboard_table #quickstart_form input',main_tabs_context).click(function(){
|
||||
Sunstone.runAction($(this).val());
|
||||
});
|
||||
};
|
||||
|
||||
function generateDashboardLinks(){
|
||||
var links="<ul>";
|
||||
for (var i=0; i<dashboard_links.length;i++){
|
||||
@ -282,7 +331,7 @@ function dashboardQuotasHTML(req, response){
|
||||
|
||||
//puts the dashboard values into "retrieving"
|
||||
function emptyDashboard(){
|
||||
$("#dashboard_tab .value_td span",main_tabs_context).html(spinner);
|
||||
$("#dashboard_tab .value_td > span",main_tabs_context).html(spinner);
|
||||
}
|
||||
|
||||
|
||||
@ -304,6 +353,60 @@ function updateDashboard(what,json_info){
|
||||
}
|
||||
}
|
||||
|
||||
function accountingSetup(){
|
||||
var context = $('#dashboard_table', main_tabs_context);
|
||||
//Enable datepicker
|
||||
$("#user_acct_from", context).datepicker({
|
||||
defaultDate: "-1d",
|
||||
changeMonth: true,
|
||||
numberOfMonths: 1,
|
||||
dateFormat: "dd/mm/yy",
|
||||
defaultDate: '-1',
|
||||
onSelect: function( selectedDate ) {
|
||||
$( "#user_acct_to", context).datepicker("option",
|
||||
"minDate",
|
||||
selectedDate );
|
||||
}
|
||||
});
|
||||
$("#user_acct_from", context).datepicker('setDate', '-1');
|
||||
|
||||
$("#user_acct_to", context).datepicker({
|
||||
defaultDate: "0",
|
||||
changeMonth: true,
|
||||
numberOfMonths: 1,
|
||||
dateFormat: "dd/mm/yy",
|
||||
maxDate: '+1',
|
||||
onSelect: function( selectedDate ) {
|
||||
$( "#user_acct_from", context).datepicker( "option",
|
||||
"maxDate",
|
||||
selectedDate );
|
||||
}
|
||||
});
|
||||
$("#user_acct_to", context).datepicker('setDate', 'Now');
|
||||
|
||||
//Listen to set date button
|
||||
$('.user_acct_date_ok', context).click(function(){
|
||||
var from = $("#user_acct_from", context).val();
|
||||
var to = $("#user_acct_to", context).val();
|
||||
|
||||
var start = $.datepicker.parseDate('dd/mm/yy', from)
|
||||
if (start){
|
||||
start = start.getTime();
|
||||
start = Math.floor(start / 1000);
|
||||
}
|
||||
|
||||
var end = $.datepicker.parseDate('dd/mm/yy', to);
|
||||
if (end){
|
||||
end = end.getTime();
|
||||
end = Math.floor(end / 1000);
|
||||
}
|
||||
|
||||
loadAccounting('User', null, user_acct_graphs,
|
||||
{ start : start, end: end });
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
$(document).ready(function(){
|
||||
//Dashboard link listener
|
||||
@ -332,7 +435,7 @@ $(document).ready(function(){
|
||||
|
||||
emptyDashboard();
|
||||
|
||||
quickstart_setup();
|
||||
accountingSetup();
|
||||
|
||||
$('#li_dashboard_tab').click(function(){
|
||||
hideDialog();
|
||||
@ -347,4 +450,5 @@ $(document).ready(function(){
|
||||
}
|
||||
});
|
||||
Sunstone.runAction("User.show", uid);
|
||||
loadAccounting('User', null, user_acct_graphs);
|
||||
});
|
@ -18,7 +18,12 @@
|
||||
<script type="text/javascript" src="vendor/jQueryLayout/jquery.layout-latest.min.js"></script>
|
||||
<script type="text/javascript" src="vendor/dataTables/jquery.dataTables.min.js"></script>
|
||||
<script type="text/javascript" src="vendor/fileuploader/fileuploader.js"></script>
|
||||
|
||||
<script type="text/javascript" src="vendor/flot/jquery.flot.min.js"></script>
|
||||
<!-- <script type="text/javascript" src="vendor/flot/jquery.flot.pie.min.js"></script> -->
|
||||
<script type="text/javascript" src="vendor/flot/jquery.flot.resize.min.js"></script>
|
||||
<!--[if lte IE 8]>
|
||||
<script type="text/javascript" src="vendor/explorercanvas/excanvas.compiled.js"></script>
|
||||
<![endif]-->
|
||||
<!-- End Vendor Libraries -->
|
||||
|
||||
<!--Languages-->
|
||||
|
@ -295,8 +295,6 @@ class SunstoneServer < CloudServer
|
||||
end
|
||||
|
||||
|
||||
# this code is meant to be replaced as accouting functionality
|
||||
# is moved to OCA. Filtering by group should perhaps be added then.
|
||||
# returns a { monitoring : meter1 : [[ts1, agg_value],[ts2, agg_value]...]
|
||||
# meter2 : [[ts1, agg_value],[ts2, agg_value]...]}
|
||||
# with this information we can paint historical graphs of usage
|
||||
@ -310,11 +308,15 @@ class SunstoneServer < CloudServer
|
||||
|
||||
acct_options = {:start_time => tstart,
|
||||
:end_time => tend}
|
||||
|
||||
# If we want acct per group, we ask for all VMs visible to user
|
||||
# and then filter by group.
|
||||
if gid
|
||||
uid = Pool::INFO_ALL
|
||||
acct_options[:group] = gid
|
||||
end
|
||||
|
||||
# Init results and request accounting
|
||||
result = {}
|
||||
meters_a = meters.split(',')
|
||||
meters_a.each do | meter |
|
||||
@ -331,11 +333,17 @@ class SunstoneServer < CloudServer
|
||||
xml = XMLElement.new
|
||||
xml.initialize_xml(acct_xml, 'HISTORY_RECORDS')
|
||||
|
||||
# We aggregate the accounting values for each interval withing
|
||||
# the given timeframe
|
||||
while tstart < tend
|
||||
|
||||
tstep = tstart + interval
|
||||
count = Hash.new
|
||||
|
||||
# We count machines which have started before the end of
|
||||
# this interval AND have not finished yet OR machines which
|
||||
# have started before the end of this interval AND
|
||||
# have finished anytime after the start of this interval
|
||||
xml.each("HISTORY[STIME<=#{tstep} and ETIME=0 or STIME<=#{tstep} and ETIME>=#{tstart}]") do |hr|
|
||||
|
||||
meters_a.each do | meter |
|
||||
@ -344,6 +352,8 @@ class SunstoneServer < CloudServer
|
||||
end
|
||||
end
|
||||
|
||||
# We have aggregated values for this interval
|
||||
# Then we just add them to the results along with a timestamp
|
||||
count.each do | mname, mcount |
|
||||
result[mname] << [tstart, mcount]
|
||||
end
|
||||
|
@ -348,7 +348,6 @@ var OpenNebula = {
|
||||
var data = params.data;
|
||||
|
||||
var method = "monitor";
|
||||
var action = OpenNebula.Helper.action(method);
|
||||
var request = OpenNebula.Helper.request(resource,method, data);
|
||||
|
||||
var url = resource.toLowerCase();
|
||||
|
@ -621,25 +621,26 @@ function escapeDoubleQuotes(string){
|
||||
//will be contained. They have some elements which ids are
|
||||
//determined by the graphs configuration, so when the time
|
||||
//of plotting comes, we can put the data in the right place.
|
||||
function generateMonitoringDivs(graphs, id_prefix){
|
||||
function generateMonitoringDivs(graphs, id_prefix, options){
|
||||
var str = "";
|
||||
//40% of the width of the screen minus
|
||||
//181px (left menu size)
|
||||
//200px (left menu size)
|
||||
var width = ($(window).width()-200)*39/100;
|
||||
var id_suffix="";
|
||||
var label="";
|
||||
var id="";
|
||||
var omit_title = options && options.omit_title;
|
||||
|
||||
$.each(graphs,function(){
|
||||
label = this.monitor_resources;
|
||||
id_suffix=label.replace(/,/g,'_');
|
||||
id_suffix=id_suffix.replace(/\//g,'_');
|
||||
id = id_prefix+id_suffix;
|
||||
str+='<table class="info_table">\
|
||||
<thead><tr><th colspan="1">'+this.title+'</th></tr></thead>\
|
||||
<tr><td id="legend_'+id_suffix+'"></td></tr>\
|
||||
<tr><td style="border:0">\
|
||||
<div id="'+id+'" style="width:'+width+'px; height:150px;margin-bottom:10px;position:relative;left:0px;">'+
|
||||
str+='<table class="info_table">'+
|
||||
(!omit_title ? '<thead><tr><th colspan="1">'+this.title+'</th></tr></thead>' : '')
|
||||
+ '<tr><td id="legend_'+id_suffix+'"></td></tr>\
|
||||
<tr><td style="border:0;width:100%;">\
|
||||
<div id="'+id+'" style="width:'+width+'px; height:150px;position:relative;left:0px;margin: 0 auto 10px auto">'+
|
||||
spinner+
|
||||
'</div>\
|
||||
</td></tr></table>';
|
||||
|
Loading…
x
Reference in New Issue
Block a user