mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
Best practice is to have one blank line at the end of Python files. Remove unneeded blank lines from the end of files
261 lines
10 KiB
Python
Executable File
261 lines
10 KiB
Python
Executable File
#!/usr/bin/python
|
|
# Licensed to the Apache Software Foundation (ASF) under one
|
|
# or more contributor license agreements. See the NOTICE file
|
|
# distributed with this work for additional information
|
|
# regarding copyright ownership. The ASF licenses this file
|
|
# to you under the Apache License, Version 2.0 (the
|
|
# "License"); you may not use this file except in compliance
|
|
# with the License. You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing,
|
|
# software distributed under the License is distributed on an
|
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
# KIND, either express or implied. See the License for the
|
|
# specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import pprint
|
|
import XenAPI
|
|
import urllib
|
|
from xml.dom import minidom
|
|
import time
|
|
import commands
|
|
|
|
# Per VM dictionary (used by RRDUpdates to look up column numbers by variable names)
|
|
class VMReport(dict):
|
|
"""Used internally by RRDUpdates"""
|
|
def __init__(self, uuid):
|
|
self.uuid = uuid
|
|
super(dict, self).__init__()
|
|
|
|
|
|
# Per Host dictionary (used by RRDUpdates to look up column numbers by variable names)
|
|
class HostReport(dict):
|
|
"""Used internally by RRDUpdates"""
|
|
def __init__(self, uuid):
|
|
self.uuid = uuid
|
|
super(dict, self).__init__()
|
|
|
|
|
|
class PerfMonException(Exception):
|
|
pass
|
|
|
|
|
|
class XmlConfigException(PerfMonException):
|
|
pass
|
|
|
|
|
|
class UsageException(Exception):
|
|
pass
|
|
|
|
|
|
class RRDUpdates:
|
|
""" Object used to get and parse the output the http://localhost/rrd_udpates?...
|
|
"""
|
|
def __init__(self):
|
|
# params are what get passed to the CGI executable in the URL
|
|
self.params = dict()
|
|
self.params['start'] = int(time.time()) - 1000 # For demo purposes!
|
|
self.params['host'] = 'false' # include data for host (as well as for VMs)
|
|
self.params['cf'] = 'AVERAGE' # consolidation function, each sample averages 12 from the 5 second RRD
|
|
self.params['interval'] = '60'
|
|
|
|
def get_nrows(self):
|
|
return self.rows
|
|
|
|
def get_vm_list(self):
|
|
return self.vm_reports.keys()
|
|
|
|
def get_vm_param_list(self, uuid):
|
|
report = self.vm_reports[uuid]
|
|
if not report:
|
|
return []
|
|
return report.keys()
|
|
|
|
def get_total_cpu_core(self, uuid):
|
|
report = self.vm_reports[uuid]
|
|
if not report:
|
|
return 0
|
|
else:
|
|
param_keys = report.keys()
|
|
result = 0
|
|
for param in param_keys:
|
|
if "cpu" in param:
|
|
result += 1
|
|
return result
|
|
|
|
def get_vm_data(self, uuid, param, row):
|
|
#pp = pprint.PrettyPrinter(indent=4)
|
|
#pp.pprint(self.vm_reports)
|
|
report = self.vm_reports[uuid]
|
|
col = report[param]
|
|
return self.__lookup_data(col, row)
|
|
|
|
def get_host_uuid(self):
|
|
report = self.host_report
|
|
if not report:
|
|
return None
|
|
return report.uuid
|
|
|
|
def get_host_param_list(self):
|
|
report = self.host_report
|
|
if not report:
|
|
return []
|
|
return report.keys()
|
|
|
|
def get_host_data(self, param, row):
|
|
report = self.host_report
|
|
col = report[param]
|
|
return self.__lookup_data(col, row)
|
|
|
|
def get_row_time(self, row):
|
|
return self.__lookup_timestamp(row)
|
|
|
|
# extract float from value (<v>) node by col,row
|
|
def __lookup_data(self, col, row):
|
|
# Note: the <rows> nodes are in reverse chronological order, and comprise
|
|
# a timestamp <t> node, followed by self.columns data <v> nodes
|
|
node = self.data_node.childNodes[self.rows - 1 - row].childNodes[col + 1]
|
|
return float(node.firstChild.toxml()) # node.firstChild should have nodeType TEXT_NODE
|
|
|
|
# extract int from value (<t>) node by row
|
|
def __lookup_timestamp(self, row):
|
|
# Note: the <rows> nodes are in reverse chronological order, and comprise
|
|
# a timestamp <t> node, followed by self.columns data <v> nodes
|
|
node = self.data_node.childNodes[self.rows - 1 - row].childNodes[0]
|
|
return int(node.firstChild.toxml()) # node.firstChild should have nodeType TEXT_NODE
|
|
|
|
def refresh(self, login, starttime, session, override_params):
|
|
self.params['start'] = starttime
|
|
params = override_params
|
|
params['session_id'] = session
|
|
params.update(self.params)
|
|
paramstr = "&".join(["%s=%s" % (k, params[k]) for k in params])
|
|
# this is better than urllib.urlopen() as it raises an Exception on http 401 'Unauthorised' error
|
|
# rather than drop into interactive mode
|
|
for host in login.host.get_all():
|
|
#print "http://" + str(login.host.get_address(host)) + "/rrd_updates?%s" % paramstr
|
|
sock = urllib.URLopener().open("http://" + str(login.host.get_address(host)) + "/rrd_updates?%s" % paramstr)
|
|
xmlsource = sock.read()
|
|
sock.close()
|
|
xmldoc = minidom.parseString(xmlsource)
|
|
self.__parse_xmldoc(xmldoc)
|
|
# Update the time used on the next run
|
|
self.params['start'] = self.end_time + 1 # avoid retrieving same data twice
|
|
|
|
def __parse_xmldoc(self, xmldoc):
|
|
# The 1st node contains meta data (description of the data)
|
|
# The 2nd node contains the data
|
|
self.meta_node = xmldoc.firstChild.childNodes[0]
|
|
self.data_node = xmldoc.firstChild.childNodes[1]
|
|
|
|
def lookup_metadata_bytag(name):
|
|
return int(self.meta_node.getElementsByTagName(name)[0].firstChild.toxml())
|
|
# rows = number of samples per variable
|
|
# columns = number of variables
|
|
self.rows = lookup_metadata_bytag('rows')
|
|
self.columns = lookup_metadata_bytag('columns')
|
|
# These indicate the period covered by the data
|
|
self.start_time = lookup_metadata_bytag('start')
|
|
self.step_time = lookup_metadata_bytag('step')
|
|
self.end_time = lookup_metadata_bytag('end')
|
|
# the <legend> Node describes the variables
|
|
self.legend = self.meta_node.getElementsByTagName('legend')[0]
|
|
# vm_reports matches uuid to per VM report
|
|
if not hasattr(self,'vm_reports'):
|
|
self.vm_reports = {}
|
|
# There is just one host_report and its uuid should not change!
|
|
self.host_report = None
|
|
# Handle each column. (I.e. each variable)
|
|
for col in range(self.columns):
|
|
self.__handle_col(col)
|
|
|
|
def __handle_col(self, col):
|
|
# work out how to interpret col from the legend
|
|
col_meta_data = self.legend.childNodes[col].firstChild.toxml()
|
|
# vm_or_host will be 'vm' or 'host'. Note that the Control domain counts as a VM!
|
|
(cf, vm_or_host, uuid, param) = col_meta_data.split(':')
|
|
if vm_or_host == 'vm':
|
|
# Create a report for this VM if it doesn't exist
|
|
if not uuid in self.vm_reports:
|
|
self.vm_reports[uuid] = VMReport(uuid)
|
|
# Update the VMReport with the col data and meta data
|
|
vm_report = self.vm_reports[uuid]
|
|
vm_report[param] = col
|
|
elif vm_or_host == 'host':
|
|
# Create a report for the host if it doesn't exist
|
|
if not self.host_report:
|
|
self.host_report = HostReport(uuid)
|
|
elif self.host_report.uuid != uuid:
|
|
raise PerfMonException("Host UUID changed: (was %s, is %s)" % (self.host_report.uuid, uuid))
|
|
# Update the HostReport with the col data and meta data
|
|
self.host_report[param] = col
|
|
else:
|
|
raise PerfMonException("Invalid string in <legend>: %s" % col_meta_data)
|
|
|
|
def getuuid(vm_name):
|
|
status, output = commands.getstatusoutput("xe vm-list | grep "+vm_name+" -B 1 | head -n 1 | awk -F':' '{print $2}' | tr -d ' '")
|
|
if (status != 0):
|
|
raise PerfMonException("Invalid vm name: %s" % vm_name)
|
|
return output
|
|
|
|
def get_vm_group_perfmon(args={}):
|
|
login = XenAPI.xapi_local()
|
|
login.login_with_password("","")
|
|
result = ""
|
|
|
|
total_vm = int(args['total_vm'])
|
|
total_counter = int(args['total_counter'])
|
|
now = int(time.time()) / 60
|
|
|
|
# Get pool's info of this host
|
|
#pool = login.xenapi.pool.get_all()[0]
|
|
# Get master node's address of pool
|
|
#master = login.xenapi.pool.get_master(pool)
|
|
#master_address = login.xenapi.host.get_address(master)
|
|
session = login._session
|
|
|
|
max_duration = 0
|
|
for counter_count in xrange(1, total_counter + 1):
|
|
duration = int(args['duration' + str(counter_count)])
|
|
if duration > max_duration:
|
|
max_duration = duration
|
|
|
|
rrd_updates = RRDUpdates()
|
|
rrd_updates.refresh(login.xenapi, now * 60 - max_duration, session, {})
|
|
|
|
#for uuid in rrd_updates.get_vm_list():
|
|
for vm_count in xrange(1, total_vm + 1):
|
|
vm_name = args['vmname' + str(vm_count)]
|
|
vm_uuid = getuuid(vm_name)
|
|
#print "Got values for VM: " + str(vm_count) + " " + vm_uuid
|
|
for counter_count in xrange(1, total_counter + 1):
|
|
#refresh average
|
|
average_cpu = 0
|
|
average_memory = 0
|
|
counter = args['counter' + str(counter_count)]
|
|
total_row = rrd_updates.get_nrows()
|
|
duration = int(args['duration' + str(counter_count)]) / 60
|
|
duration_diff = total_row - duration
|
|
if counter == "cpu":
|
|
total_cpu = rrd_updates.get_total_cpu_core(vm_uuid)
|
|
for row in xrange(duration_diff, total_row):
|
|
for cpu in xrange(0, total_cpu):
|
|
average_cpu += rrd_updates.get_vm_data(vm_uuid, "cpu" + str(cpu), row)
|
|
average_cpu /= (duration * total_cpu)
|
|
if result == "":
|
|
result += str(vm_count) + '.' + str(counter_count) + ':' + str(average_cpu)
|
|
else:
|
|
result += ',' + str(vm_count) + '.' + str(counter_count) + ':' + str(average_cpu)
|
|
elif counter == "memory":
|
|
for row in xrange(duration_diff, total_row):
|
|
average_memory += rrd_updates.get_vm_data(vm_uuid, "memory_target", row) / 1048576 - rrd_updates.get_vm_data(vm_uuid, "memory_internal_free", row) / 1024
|
|
average_memory /= duration
|
|
if result == "":
|
|
result += str(vm_count) + '.' + str(counter_count) + ':' + str(average_memory)
|
|
else:
|
|
result += ',' + str(vm_count) + '.' + str(counter_count) + ':' + str(average_memory)
|
|
return result
|