From 61962e5dc3b5573ab6495bcd25d809480a45f8f2 Mon Sep 17 00:00:00 2001 From: Javi Fontan Date: Tue, 29 Nov 2011 17:17:53 +0100 Subject: [PATCH] Feature: Added accounting tools for OpenNebula. Contributed by C12G --- install.sh | 2 + src/acct/oneacct.rb | 183 ++++++++++++++++++++++++++++++++++ src/cli/oneacct | 235 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 420 insertions(+) create mode 100644 src/acct/oneacct.rb create mode 100755 src/cli/oneacct diff --git a/install.sh b/install.sh index 669dfeee3c..6a9cccc32a 100755 --- a/install.sh +++ b/install.sh @@ -484,6 +484,7 @@ INSTALL_ETC_FILES=( BIN_FILES="src/nebula/oned \ src/scheduler/src/sched/mm_sched \ src/cli/onevm \ + src/cli/oneacct \ src/cli/onehost \ src/cli/onevnet \ src/cli/oneuser \ @@ -1180,6 +1181,7 @@ ACCT_BIN_FILES="src/acct/oneacctd" ACCT_LIB_FILES="src/acct/monitoring.rb \ src/acct/accounting.rb \ src/acct/acctd.rb \ + src/acct/oneacct.rb \ src/acct/watch_helper.rb \ src/acct/watch_client.rb" diff --git a/src/acct/oneacct.rb b/src/acct/oneacct.rb new file mode 100644 index 0000000000..2ad12cd891 --- /dev/null +++ b/src/acct/oneacct.rb @@ -0,0 +1,183 @@ +# -------------------------------------------------------------------------- +# Copyright 2010-2011, C12G Labs S.L. +# +# This file is part of OpenNebula addons. +# +# OpenNebula addons are free software: you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or the hope That it will be useful, but (at your +# option) any later version. +# +# OpenNebula addons are distributed in WITHOUT ANY WARRANTY; +# without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License +# along with OpenNebula addons. If not, see +# +# -------------------------------------------------------------------------- + + + +require 'acct/watch_helper' + +class AcctClient + def initialize(filters={}) + @filters=filters + @deltas=[] + @users={} + end + + def account(time_start=nil, time_end=nil, user_id=nil) + @filters[:start]=time_start if time_start + @filters[:end]=time_end if time_end + @filters[:user]=user_id if user_id + + get_users_consumption + + @users + end + +private + + def get_users_consumption + # Get all the deltas that match the filters + @deltas=calculate_deltas.map {|q| q.values } + + @users=slices_by_user + + user_slices_and_deltas_to_vms + end + + def slices_by_user + # Get all VM slices that match the filters + query=get_vm_slices(@filters) + + # This hash will hold the users with the resources consumed + users={} + + query.each do |reg| + vm=reg.vm + uid=vm.uid.to_i + + # Create a new user register if it still does not exist + user=users[uid]||={ + :vm_slices => [], + } + + user[:vm_slices] << reg.values + end + + users + end + + def user_slices_and_deltas_to_vms + @users.each do |user, data| + # Get the VM ids array for this user + vms=data[:vm_slices].map {|vm| vm[:id] }.sort.uniq + + data[:vms]={} + + vms.each do |vm| + # Get the slices array for this VM + slices=data[:vm_slices].select {|slice| slice[:id]==vm } + + data[:vms][vm]={ + :slices => [], + :time => 0, + } + + # Get the deltas sum for this VM + vm_delta=@deltas.find {|d| d[:vm_id]==vm } + + data[:vms][vm][:network]=vm_delta + data[:vms][vm][:vmid]=vm + + # Calculate the time consumed by the VM + slices.each do |slice| + data[:vms][vm][:slices] << slice + + time=calculate_time(slice, + @filters[:start], @filters[:end]) + data[:vms][vm][:time]+=time + end + end + + # Delete redundant slices data + data.delete(:vm_slices) + end + end + + def get_vm_slices(filters={}) + vms=WatchHelper::Register + + query=vms.join(:vms, :id => :vm_id) + query=query.filter({:vms__uid => filters[:user]}) if filters[:user] + query=query.filter( + {:retime => 0} | (:retime > filters[:start])) if filters[:start] + query=query.filter(:rstime <= filters[:end]) if filters[:end] + + query + end + + def get_deltas(filters={}) + if filters[:data] + query=filters[:data] + else + query=WatchHelper::VmDelta + end + + query=query.filter( :ptimestamp >= filters[:start] ) if filters[:start] + query=query.filter( :ptimestamp <= filters[:end] ) if filters[:end] + query=query.filter( { :vm_id => filters[:vmid] } ) if filters[:vmid] + + query + end + + def calculate_deltas + query=WatchHelper::VmDelta.select( + :ptimestamp, :vm_id, + 'sum(net_tx) AS net_tx'.lit, 'sum(net_rx) AS net_rx'.lit) + + query=query.group(:vm_id) + + new_filters=@filters.merge(:data => query) + + get_deltas(new_filters) + end + + def calculate_time(slice, period_start, period_end) + ts=slice[:rstime].to_i + te=slice[:retime].to_i + + pstart=period_start.to_i + pend=period_end.to_i + + pend=Time.now.to_i if pend==0 + + ts=pstart if tspend or te==0 + te=pend + end + + te-ts + end +end + +if $0 == __FILE__ + + require 'json' + + acct=AcctClient.new( + :start => 1319476322, + :end => 1319637455 + ) + + a=acct.account() + + puts JSON.pretty_generate(a) + +end + diff --git a/src/cli/oneacct b/src/cli/oneacct new file mode 100755 index 0000000000..065d2c09eb --- /dev/null +++ b/src/cli/oneacct @@ -0,0 +1,235 @@ +#!/usr/bin/env ruby + +# -------------------------------------------------------------------------- +# Copyright 2010-2011, C12G Labs S.L. +# +# This file is part of OpenNebula addons. +# +# OpenNebula addons are free software: you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or the hope That it will be useful, but (at your +# option) any later version. +# +# OpenNebula addons are distributed in WITHOUT ANY WARRANTY; +# without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License +# along with OpenNebula addons. If not, see +# +# -------------------------------------------------------------------------- + + +ONE_LOCATION=ENV['ONE_LOCATION'] + +$: << ONE_LOCATION+'/lib/ruby' +$: << ONE_LOCATION+'/lib/ruby/cli' + +require 'rubygems' + +require 'acct/oneacct' +require 'cli/one_helper' +require 'cli/command_parser' +require 'json' + +require 'optparse' +require 'optparse/time' + +REG_DATE=/((\d{4})\/)?(\d\d?)(\/(\d\d?))?/ +REG_TIME=/(\d\d?):(\d\d?)(:(\d\d?))?/ + +class AcctHelper + + def format_vm(options=nil) + table = CLIHelper::ShowTable.new(nil, nil) do + column :VMID, "VM ID", :size=>4 do |d| + d[:vmid] + end + + column :MEMORY, "Consumed memory", :right, :size=>8 do |d| + OpenNebulaHelper.unit_to_str( + d[:slices].first[:mem]*1024, + {}) + end + + column :CPU, "Group of the User", :right, :size=>8 do |d| + d[:slices].first[:cpu] + end + + column :NETRX, "Group of the User", :right, :size=>10 do |d| + OpenNebulaHelper.unit_to_str( + d[:network][:net_rx]/1024.0, + {}) + end + + column :NETTX, "Group of the User", :right, :size=>10 do |d| + OpenNebulaHelper.unit_to_str( + d[:network][:net_tx]/1024.0, + {}) + end + + column :TIME, "Group of the User", :right, :size=>15 do |d| + OpenNebulaHelper.time_to_str(d[:time]) + end + + default :VMID, :MEMORY, :CPU, :NETRX, :NETTX, :TIME + end + + table + end + + def list_vms(data) + format_vm().show(data) + end + + def list_users(filters) + a=gen_accounting(filters) + + a.each do |user, data| + + CLIHelper.scr_bold + CLIHelper.scr_underline + puts "# User #{user}" + CLIHelper.scr_restore + puts + + vms=data[:vms].map do |k, v| + v + end + + self.list_vms(vms) + + puts + puts + + end + end + + def gen_accounting(filters) + acct=AcctClient.new(filters) + acct.account() + end + + def gen_json(filters) + begin + require 'json' + rescue LoadError + STDERR.puts "JSON gem is needed to give the result in this format" + exit(-1) + end + + acct=gen_accounting(filters) + acct.to_json + end + + def xml_tag(tag, value) + "<#{tag}>#{value}\n" + end + + def gen_xml(filters) + acct=gen_accounting(filters) + + xml="" + + acct.each do |user, data| + xml<<"\n" + + data[:vms].each do |vmid, vm| + xml<<" \n" + + xml<<" "<\n" + + slice.each do |key, value| + xml<<" "<\n" + end + + xml<<" \n" + end + + xml<<"\n" + end + + xml + end +end + + +@options=Hash.new + +@options[:format]=:table + +opts=OptionParser.new do |opts| + opts.on('-s', '--start TIME', Time, + 'Start date and time to take into account') do |ext| + @options[:start]=ext + end + + opts.on("-e", "--end TIME", Time, + "End date and time" ) do |ext| + @options[:end]=ext + end + + opts.on("-u", "--user user", Integer, + "User id to make accounting" ) do |ext| + @options[:user]=ext.to_i + end + + opts.on("-j", "--json", + "Output in json format" ) do |ext| + @options[:format]=:json + end + + opts.on("-x", "--xml", + "Output in xml format" ) do |ext| + @options[:format]=:xml + end + + opts.on() +end + + +begin + opts.parse!(ARGV) +rescue OptionParser::ParseError => e + STDERR.puts "Error: " << e.message + exit(-1) +end + + +acct_helper=AcctHelper.new + + +filters=Hash.new + +filters[:start]=@options[:start].to_i if @options[:start] +filters[:end]=@options[:end].to_i if @options[:end] +filters[:user]=@options[:user].to_i if @options[:user] + + +case @options[:format] +when :table + acct_helper.list_users(filters) +when :json + puts acct_helper.gen_json(filters) +when :xml + puts acct_helper.gen_xml(filters) +end + + + + + +