diff --git a/tools/kvm/kvm_stat/kvm_stat b/tools/kvm/kvm_stat/kvm_stat index b4d50e8eb75a..581278c58488 100755 --- a/tools/kvm/kvm_stat/kvm_stat +++ b/tools/kvm/kvm_stat/kvm_stat @@ -10,6 +10,15 @@ # # This work is licensed under the terms of the GNU GPL, version 2. See # the COPYING file in the top-level directory. +"""The kvm_stat module outputs statistics about running KVM VMs + +Three different ways of output formatting are available: +- as a top-like text ui +- in a key -> value format +- in an all keys, all values format + +The data is sampled from the KVM's debugfs entries and its perf events. +""" import curses import sys @@ -217,8 +226,10 @@ IOCTL_NUMBERS = { } class Arch(object): - """Class that encapsulates global architecture specific data like - syscall and ioctl numbers. + """Encapsulates global architecture specific data. + + Contains the performance event open syscall and ioctl numbers, as + well as the VM exit reasons for the architecture it runs on. """ @staticmethod @@ -306,12 +317,22 @@ def parse_int_list(list_string): def get_online_cpus(): + """Returns a list of cpu id integers.""" with open('/sys/devices/system/cpu/online') as cpu_list: cpu_string = cpu_list.readline() return parse_int_list(cpu_string) def get_filters(): + """Returns a dict of trace events, their filter ids and + the values that can be filtered. + + Trace events can be filtered for special values by setting a + filter string via an ioctl. The string normally has the format + identifier==value. For each filter a new event will be created, to + be able to distinguish the events. + + """ filters = {} filters['kvm_userspace_exit'] = ('reason', USERSPACE_EXIT_REASONS) if ARCH.exit_reasons: @@ -322,6 +343,14 @@ libc = ctypes.CDLL('libc.so.6', use_errno=True) syscall = libc.syscall class perf_event_attr(ctypes.Structure): + """Struct that holds the necessary data to set up a trace event. + + For an extensive explanation see perf_event_open(2) and + include/uapi/linux/perf_event.h, struct perf_event_attr + + All fields that are not initialized in the constructor are 0. + + """ _fields_ = [('type', ctypes.c_uint32), ('size', ctypes.c_uint32), ('config', ctypes.c_uint64), @@ -342,6 +371,20 @@ class perf_event_attr(ctypes.Structure): self.read_format = PERF_FORMAT_GROUP def perf_event_open(attr, pid, cpu, group_fd, flags): + """Wrapper for the sys_perf_evt_open() syscall. + + Used to set up performance events, returns a file descriptor or -1 + on error. + + Attributes are: + - syscall number + - struct perf_event_attr * + - pid or -1 to monitor all pids + - cpu number or -1 to monitor all cpus + - The file descriptor of the group leader or -1 to create a group. + - flags + + """ return syscall(ARCH.sc_perf_evt_open, ctypes.pointer(attr), ctypes.c_int(pid), ctypes.c_int(cpu), ctypes.c_int(group_fd), ctypes.c_long(flags)) @@ -353,6 +396,8 @@ PATH_DEBUGFS_TRACING = '/sys/kernel/debug/tracing' PATH_DEBUGFS_KVM = '/sys/kernel/debug/kvm' class Group(object): + """Represents a perf event group.""" + def __init__(self): self.events = [] @@ -360,6 +405,22 @@ class Group(object): self.events.append(event) def read(self): + """Returns a dict with 'event name: value' for all events in the + group. + + Values are read by reading from the file descriptor of the + event that is the group leader. See perf_event_open(2) for + details. + + Read format for the used event configuration is: + struct read_format { + u64 nr; /* The number of events */ + struct { + u64 value; /* The value of the event */ + } values[nr]; + }; + + """ length = 8 * (1 + len(self.events)) read_format = 'xxxxxxxx' + 'Q' * len(self.events) return dict(zip([event.name for event in self.events], @@ -367,6 +428,7 @@ class Group(object): os.read(self.events[0].fd, length)))) class Event(object): + """Represents a performance event and manages its life cycle.""" def __init__(self, name, group, trace_cpu, trace_pid, trace_point, trace_filter, trace_set='kvm'): self.name = name @@ -375,10 +437,19 @@ class Event(object): trace_filter, trace_set) def __del__(self): + """Closes the event's file descriptor. + + As no python file object was created for the file descriptor, + python will not reference count the descriptor and will not + close it itself automatically, so we do it. + + """ if self.fd: os.close(self.fd) def setup_event_attribute(self, trace_set, trace_point): + """Returns an initialized ctype perf_event_attr struct.""" + id_path = os.path.join(PATH_DEBUGFS_TRACING, 'events', trace_set, trace_point, 'id') @@ -388,9 +459,19 @@ class Event(object): def setup_event(self, group, trace_cpu, trace_pid, trace_point, trace_filter, trace_set): + """Sets up the perf event in Linux. + + Issues the syscall to register the event in the kernel and + then sets the optional filter. + + """ + event_attr = self.setup_event_attribute(trace_set, trace_point) + # First event will be group leader. group_leader = -1 + + # All others have to pass the leader's descriptor instead. if group.events: group_leader = group.events[0].fd @@ -408,15 +489,33 @@ class Event(object): self.fd = fd def enable(self): + """Enables the trace event in the kernel. + + Enabling the group leader makes reading counters from it and the + events under it possible. + + """ fcntl.ioctl(self.fd, ARCH.ioctl_numbers['ENABLE'], 0) def disable(self): + """Disables the trace event in the kernel. + + Disabling the group leader makes reading all counters under it + impossible. + + """ fcntl.ioctl(self.fd, ARCH.ioctl_numbers['DISABLE'], 0) def reset(self): + """Resets the count of the trace event in the kernel.""" fcntl.ioctl(self.fd, ARCH.ioctl_numbers['RESET'], 0) class TracepointProvider(object): + """Data provider for the stats class. + + Manages the events/groups from which it acquires its data. + + """ def __init__(self): self.group_leaders = [] self.filters = get_filters() @@ -424,6 +523,20 @@ class TracepointProvider(object): self._pid = 0 def get_available_fields(self): + """Returns a list of available event's of format 'event name(filter + name)'. + + All available events have directories under + /sys/kernel/debug/tracing/events/ which export information + about the specific event. Therefore, listing the dirs gives us + a list of all available events. + + Some events like the vm exit reasons can be filtered for + specific values. To take account for that, the routine below + creates special fields with the following format: + event name(filter name) + + """ path = os.path.join(PATH_DEBUGFS_TRACING, 'events', 'kvm') fields = walkdir(path)[1] extra = [] @@ -436,6 +549,8 @@ class TracepointProvider(object): return fields def setup_traces(self): + """Creates all event and group objects needed to be able to retrieve + data.""" if self._pid > 0: # Fetch list of all threads of the monitored pid, as qemu # starts a thread for each vcpu. @@ -499,6 +614,7 @@ class TracepointProvider(object): @fields.setter def fields(self, fields): + """Enables/disables the (un)wanted events""" self._fields = fields for group in self.group_leaders: for index, event in enumerate(group.events): @@ -517,12 +633,16 @@ class TracepointProvider(object): @pid.setter def pid(self, pid): + """Changes the monitored pid by setting new traces.""" self._pid = pid + # The garbage collector will get rid of all Event/Group + # objects and open files after removing the references. self.group_leaders = [] self.setup_traces() self.fields = self._fields def read(self): + """Returns 'event name: current value' for all enabled events.""" ret = defaultdict(int) for group in self.group_leaders: for name, val in group.read().iteritems(): @@ -531,12 +651,19 @@ class TracepointProvider(object): return ret class DebugfsProvider(object): + """Provides data from the files that KVM creates in the kvm debugfs + folder.""" def __init__(self): self._fields = self.get_available_fields() self._pid = 0 self.do_read = True def get_available_fields(self): + """"Returns a list of available fields. + + The fields are all available KVM debugfs files + + """ return walkdir(PATH_DEBUGFS_KVM)[2] @property @@ -592,6 +719,12 @@ class DebugfsProvider(object): return 0 class Stats(object): + """Manages the data providers and the data they provide. + + It is used to set filters on the provider's data and collect all + provider data. + + """ def __init__(self, providers, pid, fields=None): self.providers = providers self._pid_filter = pid @@ -601,6 +734,7 @@ class Stats(object): self.update_provider_filters() def update_provider_filters(self): + """Propagates fields filters to providers.""" def wanted(key): if not self._fields_filter: return True @@ -615,6 +749,7 @@ class Stats(object): provider.fields = provider_fields def update_provider_pid(self): + """Propagates pid filters to providers.""" for provider in self.providers: provider.pid = self._pid_filter @@ -638,6 +773,8 @@ class Stats(object): self.update_provider_pid() def get(self): + """Returns a dict with field -> (value, delta to last value) of all + provider data.""" for provider in self.providers: new = provider.read() for key in provider.fields: @@ -653,6 +790,7 @@ LABEL_WIDTH = 40 NUMBER_WIDTH = 10 class Tui(object): + """Instruments curses to draw a nice text ui.""" def __init__(self, stats): self.stats = stats self.screen = None @@ -687,6 +825,7 @@ class Tui(object): curses.endwin() def update_drilldown(self): + """Sets or removes a filter that only allows fields without braces.""" if not self.stats.fields_filter: self.stats.fields_filter = r'^[^\(]*$' @@ -694,9 +833,11 @@ class Tui(object): self.stats.fields_filter = None def update_pid(self, pid): + """Propagates pid selection to stats object.""" self.stats.pid_filter = pid def refresh(self, sleeptime): + """Refreshes on-screen data.""" self.screen.erase() if self.stats.pid_filter > 0: self.screen.addstr(0, 0, 'kvm statistics - pid {0}' @@ -734,6 +875,11 @@ class Tui(object): self.screen.refresh() def show_filter_selection(self): + """Draws filter selection mask. + + Asks for a valid regex and sets the fields filter accordingly. + + """ while True: self.screen.erase() self.screen.addstr(0, 0, @@ -756,6 +902,11 @@ class Tui(object): continue def show_vm_selection(self): + """Draws PID selection mask. + + Asks for a pid until a valid pid or 0 has been entered. + + """ while True: self.screen.erase() self.screen.addstr(0, 0, @@ -787,6 +938,7 @@ class Tui(object): continue def show_stats(self): + """Refreshes the screen and processes user input.""" sleeptime = 0.25 while True: self.refresh(sleeptime) @@ -809,6 +961,7 @@ class Tui(object): continue def batch(stats): + """Prints statistics in a key, value format.""" s = stats.get() time.sleep(1) s = stats.get() @@ -817,6 +970,7 @@ def batch(stats): print '%-42s%10d%10d' % (key, values[0], values[1]) def log(stats): + """Prints statistics as reiterating key block, multiple value blocks.""" keys = sorted(stats.get().iterkeys()) def banner(): for k in keys: @@ -837,6 +991,7 @@ def log(stats): line += 1 def get_options(): + """Returns processed program arguments.""" description_text = """ This script displays various statistics about VMs running under KVM. The statistics are gathered from the KVM debugfs entries and / or the @@ -906,6 +1061,7 @@ Requirements: return options def get_providers(options): + """Returns a list of data providers depending on the passed options.""" providers = [] if options.tracepoints: @@ -918,6 +1074,7 @@ def get_providers(options): return providers def check_access(options): + """Exits if the current user can't access all needed directories.""" if not os.path.exists('/sys/kernel/debug'): sys.stderr.write('Please enable CONFIG_DEBUG_FS in your kernel.') sys.exit(1)