Files
Andrew Cooper 1b00068420 tools: Delete trailing whitespace in python scripts
No functional change.

Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
Reviewed-by: Anthony PERARD <anthony.perard@citrix.com>
Acked-by: Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>
2023-03-17 10:44:16 +00:00

714 lines
24 KiB
Python

#!/usr/bin/env python
#####################################################################
# xenmon is a front-end for xenbaked.
# There is a curses interface for live monitoring. XenMon also allows
# logging to a file. For options, run python xenmon.py -h
#
# Copyright (C) 2005,2006 by Hewlett Packard, Palo Alto and Fort Collins
# Authors: Lucy Cherkasova, lucy.cherkasova@hp.com
# Rob Gardner, rob.gardner@hp.com
# Diwaker Gupta, diwaker.gupta@hp.com
#####################################################################
# This program is 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; under version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but 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 this program; If not, see <http://www.gnu.org/licenses/>.
#####################################################################
from __future__ import print_function
import mmap
import struct
import os
import time
import optparse as _o
import curses as _c
import math
import sys
# constants
NSAMPLES = 100
NDOMAINS = 32
IDLE_DOMAIN = -1 # idle domain's ID
# the struct strings for qos_info
ST_DOM_INFO = "6Q3i2H32s"
ST_QDATA = "%dQ" % (6*NDOMAINS + 4)
# size of mmaped file
QOS_DATA_SIZE = struct.calcsize(ST_QDATA)*NSAMPLES + struct.calcsize(ST_DOM_INFO)*NDOMAINS + struct.calcsize("4i")
# location of mmaped file, hard coded right now
SHM_FILE = "/var/run/xenq-shm"
# format strings
TOTALS = 15*' ' + "%6.2f%%" + 35*' ' + "%6.2f%%"
ALLOCATED = "Allocated"
GOTTEN = "Gotten"
BLOCKED = "Blocked"
WAITED = "Waited"
IOCOUNT = "I/O Count"
EXCOUNT = "Exec Count"
# globals
dom_in_use = []
# our curses screen
stdscr = None
# parsed options
options, args = None, None
# the optparse module is quite smart
# to see help, just run xenmon -h
def setup_cmdline_parser():
parser = _o.OptionParser()
parser.add_option("-l", "--live", dest="live", action="store_true",
default=True, help = "show the ncurses live monitoring frontend (default)")
parser.add_option("-n", "--notlive", dest="live", action="store_false",
default="True", help = "write to file instead of live monitoring")
parser.add_option("-p", "--prefix", dest="prefix",
default = "log", help="prefix to use for output files")
parser.add_option("-t", "--time", dest="duration",
action="store", type="int", default=10,
help="stop logging to file after this much time has elapsed (in seconds). set to 0 to keep logging indefinitely")
parser.add_option("-i", "--interval", dest="interval",
action="store", type="int", default=1000,
help="interval for logging (in ms)")
parser.add_option("--ms_per_sample", dest="mspersample",
action="store", type="int", default=100,
help = "determines how many ms worth of data goes in a sample")
parser.add_option("--cpu", dest="cpu", action="store", type="int", default=0,
help = "specifies which cpu to display data for")
parser.add_option("--allocated", dest="allocated", action="store_true",
default=False, help="Display allocated time for each domain")
parser.add_option("--noallocated", dest="allocated", action="store_false",
default=False, help="Don't display allocated time for each domain")
parser.add_option("--blocked", dest="blocked", action="store_true",
default=True, help="Display blocked time for each domain")
parser.add_option("--noblocked", dest="blocked", action="store_false",
default=True, help="Don't display blocked time for each domain")
parser.add_option("--waited", dest="waited", action="store_true",
default=True, help="Display waiting time for each domain")
parser.add_option("--nowaited", dest="waited", action="store_false",
default=True, help="Don't display waiting time for each domain")
parser.add_option("--excount", dest="excount", action="store_true",
default=False, help="Display execution count for each domain")
parser.add_option("--noexcount", dest="excount", action="store_false",
default=False, help="Don't display execution count for each domain")
parser.add_option("--iocount", dest="iocount", action="store_true",
default=False, help="Display I/O count for each domain")
parser.add_option("--noiocount", dest="iocount", action="store_false",
default=False, help="Don't display I/O count for each domain")
return parser
# encapsulate information about a domain
class DomainInfo(object):
def __init__(self):
self.allocated_sum = 0
self.gotten_sum = 0
self.blocked_sum = 0
self.waited_sum = 0
self.exec_count = 0;
self.iocount_sum = 0
self.ffp_samples = []
def gotten_stats(self, passed):
total = float(self.gotten_sum)
per = 100*total/passed
exs = self.exec_count
if exs > 0:
avg = total/exs
else:
avg = 0
return [total/(float(passed)/10**9), per, avg]
def waited_stats(self, passed):
total = float(self.waited_sum)
per = 100*total/passed
exs = self.exec_count
if exs > 0:
avg = total/exs
else:
avg = 0
return [total/(float(passed)/10**9), per, avg]
def blocked_stats(self, passed):
total = float(self.blocked_sum)
per = 100*total/passed
ios = self.iocount_sum
if ios > 0:
avg = total/float(ios)
else:
avg = 0
return [total/(float(passed)/10**9), per, avg]
def allocated_stats(self, passed):
total = self.allocated_sum
exs = self.exec_count
if exs > 0:
return float(total)/exs
else:
return 0
def ec_stats(self, passed):
total = float(self.exec_count/(float(passed)/10**9))
return total
def io_stats(self, passed):
total = float(self.iocount_sum)
exs = self.exec_count
if exs > 0:
avg = total/exs
else:
avg = 0
return [total/(float(passed)/10**9), avg]
def stats(self, passed):
return [self.gotten_stats(passed), self.allocated_stats(passed), self.blocked_stats(passed),
self.waited_stats(passed), self.ec_stats(passed), self.io_stats(passed)]
# report values over desired interval
def summarize(startat, endat, duration, samples):
dominfos = {}
for i in range(0, NDOMAINS):
dominfos[i] = DomainInfo()
passed = 1 # to prevent zero division
curid = startat
numbuckets = 0
lost_samples = []
ffp_samples = []
while passed < duration:
for i in range(0, NDOMAINS):
if dom_in_use[i]:
dominfos[i].gotten_sum += samples[curid][0*NDOMAINS + i]
dominfos[i].allocated_sum += samples[curid][1*NDOMAINS + i]
dominfos[i].waited_sum += samples[curid][2*NDOMAINS + i]
dominfos[i].blocked_sum += samples[curid][3*NDOMAINS + i]
dominfos[i].exec_count += samples[curid][4*NDOMAINS + i]
dominfos[i].iocount_sum += samples[curid][5*NDOMAINS + i]
passed += samples[curid][6*NDOMAINS]
lost_samples.append(samples[curid][6*NDOMAINS + 2])
ffp_samples.append(samples[curid][6*NDOMAINS + 3])
numbuckets += 1
if curid > 0:
curid -= 1
else:
curid = NSAMPLES - 1
if curid == endat:
break
lostinfo = [min(lost_samples), sum(lost_samples), max(lost_samples)]
ffpinfo = [min(ffp_samples), sum(ffp_samples), max(ffp_samples)]
ldoms = []
for x in range(0, NDOMAINS):
if dom_in_use[x]:
ldoms.append(dominfos[x].stats(passed))
else:
ldoms.append(0)
return [ldoms, lostinfo, ffpinfo]
# scale microseconds to milliseconds or seconds as necessary
def time_scale(ns):
if ns < 1000:
return "%4.2f ns" % float(ns)
elif ns < 1000*1000:
return "%4.2f us" % (float(ns)/10**3)
elif ns < 10**9:
return "%4.2f ms" % (float(ns)/10**6)
else:
return "%4.2f s" % (float(ns)/10**9)
# paint message on curses screen, but detect screen size errors
def display(scr, row, col, str, attr=0):
try:
scr.addstr(row, col, str, attr)
except:
scr.erase()
_c.nocbreak()
scr.keypad(0)
_c.echo()
_c.endwin()
print("Your terminal screen is not big enough; Please resize it.")
print("row=%d, col=%d, str='%s'" % (row, col, str))
sys.exit(1)
# diplay domain id
def display_domain_id(scr, row, col, dom):
if dom == IDLE_DOMAIN:
display(scr, row, col-1, "Idle")
else:
display(scr, row, col, "%d" % dom)
# the live monitoring code
def show_livestats(cpu):
ncpu = 1 # number of cpu's on this platform
slen = 0 # size of shared data structure, incuding padding
cpu_1sec_usage = 0.0
cpu_10sec_usage = 0.0
heartbeat = 1
global dom_in_use, options
# mmap the (the first chunk of the) file
shmf = open(SHM_FILE, "r+")
shm = mmap.mmap(shmf.fileno(), QOS_DATA_SIZE)
# initialize curses
stdscr = _c.initscr()
_c.noecho()
_c.cbreak()
stdscr.keypad(1)
stdscr.timeout(1000)
[maxy, maxx] = stdscr.getmaxyx()
# display in a loop
while True:
cpuidx = 0
while cpuidx < ncpu:
# calculate offset in mmap file to start from
idx = cpuidx * slen
samples = []
doms = []
dom_in_use = []
domain_id = []
# read in data
for i in range(0, NSAMPLES):
len = struct.calcsize(ST_QDATA)
sample = struct.unpack(ST_QDATA, shm[idx:idx+len])
samples.append(sample)
idx += len
for i in range(0, NDOMAINS):
len = struct.calcsize(ST_DOM_INFO)
dom = struct.unpack(ST_DOM_INFO, shm[idx:idx+len])
doms.append(dom)
# (last_update_time, start_time, runnable_start_time, blocked_start_time,
# ns_since_boot, ns_oncpu_since_boot, runnable_at_last_update,
# runnable, in_use, domid, junk, name) = dom
# dom_in_use.append(in_use)
dom_in_use.append(dom[8])
domid = dom[9]
if domid == 32767 :
domid = IDLE_DOMAIN
domain_id.append(domid)
idx += len
# print "dom_in_use(cpu=%d): " % cpuidx, dom_in_use
len = struct.calcsize("4i")
oldncpu = ncpu
(next, ncpu, slen, freq) = struct.unpack("4i", shm[idx:idx+len])
idx += len
# xenbaked tells us how many cpu's it's got, so re-do
# the mmap if necessary to get multiple cpu data
if oldncpu != ncpu:
shm = mmap.mmap(shmf.fileno(), ncpu*slen)
# if we've just calculated data for the cpu of interest, then
# stop examining mmap data and start displaying stuff
if cpuidx == cpu:
break
cpuidx = cpuidx + 1
# calculate starting and ending datapoints; never look at "next" since
# it represents live data that may be in transition.
startat = next - 1
if next + 10 < NSAMPLES:
endat = next + 10
else:
endat = 10
# get summary over desired interval
[h1, l1, f1] = summarize(startat, endat, 10**9, samples)
[h2, l2, f2] = summarize(startat, endat, 10 * 10**9, samples)
# the actual display code
row = 0
display(stdscr, row, 1, "CPU = %d" % cpu, _c.A_STANDOUT)
display(stdscr, row, 10, "%sLast 10 seconds (%3.2f%%)%sLast 1 second (%3.2f%%)" % (6*' ', cpu_10sec_usage, 30*' ', cpu_1sec_usage), _c.A_BOLD)
row +=1
display(stdscr, row, 1, "%s" % ((maxx-2)*'='))
total_h1_cpu = 0
total_h2_cpu = 0
cpu_1sec_usage = 0.0
cpu_10sec_usage = 0.0
for dom in range(0, NDOMAINS):
if not dom_in_use[dom]:
continue
if h1[dom][0][1] > 0 or domain_id[dom] == IDLE_DOMAIN:
# display gotten
row += 1
col = 2
display_domain_id(stdscr, row, col, domain_id[dom])
col += 4
display(stdscr, row, col, "%s" % time_scale(h2[dom][0][0]))
col += 12
display(stdscr, row, col, "%3.2f%%" % h2[dom][0][1])
if dom != IDLE_DOMAIN:
cpu_10sec_usage += h2[dom][0][1]
col += 12
display(stdscr, row, col, "%s/ex" % time_scale(h2[dom][0][2]))
col += 18
display(stdscr, row, col, "%s" % time_scale(h1[dom][0][0]))
col += 12
display(stdscr, row, col, "%3.2f%%" % h1[dom][0][1], _c.A_STANDOUT)
col += 12
display(stdscr, row, col, "%s/ex" % time_scale(h1[dom][0][2]))
col += 18
display(stdscr, row, col, "Gotten")
if dom != IDLE_DOMAIN:
cpu_1sec_usage = cpu_1sec_usage + h1[dom][0][1]
# display allocated
if options.allocated:
row += 1
col = 2
display_domain_id(stdscr, row, col, domain_id[dom])
col += 28
display(stdscr, row, col, "%s/ex" % time_scale(h2[dom][1]))
col += 42
display(stdscr, row, col, "%s/ex" % time_scale(h1[dom][1]))
col += 18
display(stdscr, row, col, "Allocated")
# display blocked
if options.blocked:
row += 1
col = 2
display_domain_id(stdscr, row, col, domain_id[dom])
col += 4
display(stdscr, row, col, "%s" % time_scale(h2[dom][2][0]))
col += 12
display(stdscr, row, col, "%3.2f%%" % h2[dom][2][1])
col += 12
display(stdscr, row, col, "%s/io" % time_scale(h2[dom][2][2]))
col += 18
display(stdscr, row, col, "%s" % time_scale(h1[dom][2][0]))
col += 12
display(stdscr, row, col, "%3.2f%%" % h1[dom][2][1])
col += 12
display(stdscr, row, col, "%s/io" % time_scale(h1[dom][2][2]))
col += 18
display(stdscr, row, col, "Blocked")
# display waited
if options.waited:
row += 1
col = 2
display_domain_id(stdscr, row, col, domain_id[dom])
col += 4
display(stdscr, row, col, "%s" % time_scale(h2[dom][3][0]))
col += 12
display(stdscr, row, col, "%3.2f%%" % h2[dom][3][1])
col += 12
display(stdscr, row, col, "%s/ex" % time_scale(h2[dom][3][2]))
col += 18
display(stdscr, row, col, "%s" % time_scale(h1[dom][3][0]))
col += 12
display(stdscr, row, col, "%3.2f%%" % h1[dom][3][1])
col += 12
display(stdscr, row, col, "%s/ex" % time_scale(h1[dom][3][2]))
col += 18
display(stdscr, row, col, "Waited")
# display ex count
if options.excount:
row += 1
col = 2
display_domain_id(stdscr, row, col, domain_id[dom])
col += 28
display(stdscr, row, col, "%d/s" % h2[dom][4])
col += 42
display(stdscr, row, col, "%d" % h1[dom][4])
col += 18
display(stdscr, row, col, "Execution count")
# display io count
if options.iocount:
row += 1
col = 2
display_domain_id(stdscr, row, col, domain_id[dom])
col += 4
display(stdscr, row, col, "%d/s" % h2[dom][5][0])
col += 24
display(stdscr, row, col, "%d/ex" % h2[dom][5][1])
col += 18
display(stdscr, row, col, "%d" % h1[dom][5][0])
col += 24
display(stdscr, row, col, "%3.2f/ex" % h1[dom][5][1])
col += 18
display(stdscr, row, col, "I/O Count")
#row += 1
#stdscr.hline(row, 1, '-', maxx - 2)
total_h1_cpu += h1[dom][0][1]
total_h2_cpu += h2[dom][0][1]
row += 1
star = heartbeat * '*'
heartbeat = 1 - heartbeat
display(stdscr, row, 1, star)
display(stdscr, row, 2, TOTALS % (total_h2_cpu, total_h1_cpu))
row += 1
# display(stdscr, row, 2,
# "\tFFP: %d (Min: %d, Max: %d)\t\t\tFFP: %d (Min: %d, Max %d)" %
# (math.ceil(f2[1]), f2[0], f2[2], math.ceil(f1[1]), f1[0], f1[2]), _c.A_BOLD)
if l1[1] > 1 :
row += 1
display(stdscr, row, 2,
"\tRecords lost: %d (Min: %d, Max: %d)\t\t\tRecords lost: %d (Min: %d, Max %d)" %
(math.ceil(l2[1]), l2[0], l2[2], math.ceil(l1[1]), l1[0], l1[2]), _c.A_BOLD)
# grab a char from tty input; exit if interrupt hit
try:
c = stdscr.getch()
except:
break
# q = quit
if c == ord('q'):
break
# c = cycle to a new cpu of interest
if c == ord('c'):
cpu = (cpu + 1) % ncpu
# n/p = cycle to the next/previous CPU
if c == ord('n'):
cpu = (cpu + 1) % ncpu
if c == ord('p'):
cpu = (cpu - 1) % ncpu
stdscr.erase()
_c.nocbreak()
stdscr.keypad(0)
_c.echo()
_c.endwin()
shm.close()
shmf.close()
# simple functions to allow initialization of log files without actually
# physically creating files that are never used; only on the first real
# write does the file get created
class Delayed(object):
def __init__(self, filename, mode):
self.filename = filename
self.saved_mode = mode
self.delay_data = ""
self.opened = 0
def delayed_write(self, str):
self.delay_data = str
def write(self, str):
if not self.opened:
self.file = open(self.filename, self.saved_mode)
self.opened = 1
self.file.write(self.delay_data)
self.file.write(str)
def rename(self, name):
self.filename = name
def flush(self):
if self.opened:
self.file.flush()
def close(self):
if self.opened:
self.file.close()
def writelog():
global options
global dom_in_use
ncpu = 1 # number of cpu's
slen = 0 # size of shared structure inc. padding
shmf = open(SHM_FILE, "r+")
shm = mmap.mmap(shmf.fileno(), QOS_DATA_SIZE)
interval = 0
curr = last = time.time()
outfiles = {}
for dom in range(0, NDOMAINS):
outfiles[dom] = Delayed("%s-dom%d.log" % (options.prefix, dom), 'w')
outfiles[dom].delayed_write("# passed cpu dom cpu(tot) cpu(%) cpu/ex allocated/ex blocked(tot) blocked(%) blocked/io waited(tot) waited(%) waited/ex ex/s io(tot) io/ex\n")
while options.duration == 0 or interval < (options.duration * 1000):
cpuidx = 0
while cpuidx < ncpu:
idx = cpuidx * slen # offset needed in mmap file
samples = []
doms = []
dom_in_use = []
domain_id = []
for i in range(0, NSAMPLES):
len = struct.calcsize(ST_QDATA)
sample = struct.unpack(ST_QDATA, shm[idx:idx+len])
samples.append(sample)
idx += len
for i in range(0, NDOMAINS):
len = struct.calcsize(ST_DOM_INFO)
dom = struct.unpack(ST_DOM_INFO, shm[idx:idx+len])
# doms.append(dom)
# (last_update_time, start_time, runnable_start_time, blocked_start_time,
# ns_since_boot, ns_oncpu_since_boot, runnable_at_last_update,
# runnable, in_use, domid, junk, name) = dom
dom_in_use.append(dom[8])
domid = dom[9]
if domid == 32767:
domid = IDLE_DOMAIN
domain_id.append(domid)
if domid == IDLE_DOMAIN:
outfiles[i].rename("%s-idle.log" % options.prefix)
else:
outfiles[i].rename("%s-dom%d.log" % (options.prefix, domid))
idx += len
len = struct.calcsize("4i")
oldncpu = ncpu
(next, ncpu, slen, freq) = struct.unpack("4i", shm[idx:idx+len])
idx += len
if oldncpu != ncpu:
shm = mmap.mmap(shmf.fileno(), ncpu*slen)
startat = next - 1
if next + 10 < NSAMPLES:
endat = next + 10
else:
endat = 10
[h1,l1, f1] = summarize(startat, endat, options.interval * 10**6, samples)
for dom in range(0, NDOMAINS):
if not dom_in_use[dom]:
continue
if h1[dom][0][1] > 0 or dom == IDLE_DOMAIN:
outfiles[dom].write("%.3f %d %d %.3f %.3f %.3f %.3f %.3f %.3f %.3f %.3f %.3f %.3f %.3f %.3f %.3f\n" %
(interval, cpuidx, domain_id[dom],
h1[dom][0][0], h1[dom][0][1], h1[dom][0][2],
h1[dom][1],
h1[dom][2][0], h1[dom][2][1], h1[dom][2][2],
h1[dom][3][0], h1[dom][3][1], h1[dom][3][2],
h1[dom][4],
h1[dom][5][0], h1[dom][5][1]))
outfiles[dom].flush()
curr = time.time()
interval += (curr - last) * 1000
last = curr
cpuidx = cpuidx + 1
time.sleep(options.interval / 1000.0)
for dom in range(0, NDOMAINS):
outfiles[dom].close()
# start xenbaked
def start_xenbaked():
global options
global kill_cmd
global xenbaked_cmd
os.system(kill_cmd)
os.system(xenbaked_cmd + " --ms_per_sample=%d &" %
options.mspersample)
time.sleep(1)
# stop xenbaked
def stop_xenbaked():
global stop_cmd
os.system(stop_cmd)
def main():
global options
global args
global domains
global stop_cmd
global kill_cmd
global xenbaked_cmd
if os.uname()[0] == "SunOS":
xenbaked_cmd = "/usr/lib/xenbaked"
stop_cmd = "/usr/bin/pkill -INT -z global xenbaked"
kill_cmd = "/usr/bin/pkill -KILL -z global xenbaked"
else:
# assumes that xenbaked is in your path
xenbaked_cmd = "xenbaked"
stop_cmd = "/usr/bin/pkill -INT xenbaked"
kill_cmd = "/usr/bin/pkill -KILL xenbaked"
parser = setup_cmdline_parser()
(options, args) = parser.parse_args()
if len(args):
parser.error("No parameter required")
if options.mspersample < 0:
parser.error("option --ms_per_sample: invalid negative value: '%d'" %
options.mspersample)
# If --ms_per_sample= is too large, no data may be logged.
if not options.live and options.duration != 0 and \
options.mspersample > options.duration * 1000:
parser.error("option --ms_per_sample: too large (> %d ms)" %
(options.duration * 1000))
start_xenbaked()
if options.live:
show_livestats(options.cpu)
else:
try:
writelog()
except:
print('Quitting.')
stop_xenbaked()
if __name__ == "__main__":
main()