#!/usr/bin/python 
 | 
# 
 | 
# top-like utility for displaying kvm statistics 
 | 
# 
 | 
# Copyright 2006-2008 Qumranet Technologies 
 | 
# Copyright 2008-2011 Red Hat, Inc. 
 | 
# 
 | 
# Authors: 
 | 
#  Avi Kivity <avi@redhat.com> 
 | 
# 
 | 
# 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. 
 | 
""" 
 | 
from __future__ import print_function 
 | 
  
 | 
import curses 
 | 
import sys 
 | 
import locale 
 | 
import os 
 | 
import time 
 | 
import optparse 
 | 
import ctypes 
 | 
import fcntl 
 | 
import resource 
 | 
import struct 
 | 
import re 
 | 
import subprocess 
 | 
from collections import defaultdict, namedtuple 
 | 
  
 | 
VMX_EXIT_REASONS = { 
 | 
    'EXCEPTION_NMI':        0, 
 | 
    'EXTERNAL_INTERRUPT':   1, 
 | 
    'TRIPLE_FAULT':         2, 
 | 
    'PENDING_INTERRUPT':    7, 
 | 
    'NMI_WINDOW':           8, 
 | 
    'TASK_SWITCH':          9, 
 | 
    'CPUID':                10, 
 | 
    'HLT':                  12, 
 | 
    'INVLPG':               14, 
 | 
    'RDPMC':                15, 
 | 
    'RDTSC':                16, 
 | 
    'VMCALL':               18, 
 | 
    'VMCLEAR':              19, 
 | 
    'VMLAUNCH':             20, 
 | 
    'VMPTRLD':              21, 
 | 
    'VMPTRST':              22, 
 | 
    'VMREAD':               23, 
 | 
    'VMRESUME':             24, 
 | 
    'VMWRITE':              25, 
 | 
    'VMOFF':                26, 
 | 
    'VMON':                 27, 
 | 
    'CR_ACCESS':            28, 
 | 
    'DR_ACCESS':            29, 
 | 
    'IO_INSTRUCTION':       30, 
 | 
    'MSR_READ':             31, 
 | 
    'MSR_WRITE':            32, 
 | 
    'INVALID_STATE':        33, 
 | 
    'MWAIT_INSTRUCTION':    36, 
 | 
    'MONITOR_INSTRUCTION':  39, 
 | 
    'PAUSE_INSTRUCTION':    40, 
 | 
    'MCE_DURING_VMENTRY':   41, 
 | 
    'TPR_BELOW_THRESHOLD':  43, 
 | 
    'APIC_ACCESS':          44, 
 | 
    'EPT_VIOLATION':        48, 
 | 
    'EPT_MISCONFIG':        49, 
 | 
    'WBINVD':               54, 
 | 
    'XSETBV':               55, 
 | 
    'APIC_WRITE':           56, 
 | 
    'INVPCID':              58, 
 | 
} 
 | 
  
 | 
SVM_EXIT_REASONS = { 
 | 
    'READ_CR0':       0x000, 
 | 
    'READ_CR3':       0x003, 
 | 
    'READ_CR4':       0x004, 
 | 
    'READ_CR8':       0x008, 
 | 
    'WRITE_CR0':      0x010, 
 | 
    'WRITE_CR3':      0x013, 
 | 
    'WRITE_CR4':      0x014, 
 | 
    'WRITE_CR8':      0x018, 
 | 
    'READ_DR0':       0x020, 
 | 
    'READ_DR1':       0x021, 
 | 
    'READ_DR2':       0x022, 
 | 
    'READ_DR3':       0x023, 
 | 
    'READ_DR4':       0x024, 
 | 
    'READ_DR5':       0x025, 
 | 
    'READ_DR6':       0x026, 
 | 
    'READ_DR7':       0x027, 
 | 
    'WRITE_DR0':      0x030, 
 | 
    'WRITE_DR1':      0x031, 
 | 
    'WRITE_DR2':      0x032, 
 | 
    'WRITE_DR3':      0x033, 
 | 
    'WRITE_DR4':      0x034, 
 | 
    'WRITE_DR5':      0x035, 
 | 
    'WRITE_DR6':      0x036, 
 | 
    'WRITE_DR7':      0x037, 
 | 
    'EXCP_BASE':      0x040, 
 | 
    'INTR':           0x060, 
 | 
    'NMI':            0x061, 
 | 
    'SMI':            0x062, 
 | 
    'INIT':           0x063, 
 | 
    'VINTR':          0x064, 
 | 
    'CR0_SEL_WRITE':  0x065, 
 | 
    'IDTR_READ':      0x066, 
 | 
    'GDTR_READ':      0x067, 
 | 
    'LDTR_READ':      0x068, 
 | 
    'TR_READ':        0x069, 
 | 
    'IDTR_WRITE':     0x06a, 
 | 
    'GDTR_WRITE':     0x06b, 
 | 
    'LDTR_WRITE':     0x06c, 
 | 
    'TR_WRITE':       0x06d, 
 | 
    'RDTSC':          0x06e, 
 | 
    'RDPMC':          0x06f, 
 | 
    'PUSHF':          0x070, 
 | 
    'POPF':           0x071, 
 | 
    'CPUID':          0x072, 
 | 
    'RSM':            0x073, 
 | 
    'IRET':           0x074, 
 | 
    'SWINT':          0x075, 
 | 
    'INVD':           0x076, 
 | 
    'PAUSE':          0x077, 
 | 
    'HLT':            0x078, 
 | 
    'INVLPG':         0x079, 
 | 
    'INVLPGA':        0x07a, 
 | 
    'IOIO':           0x07b, 
 | 
    'MSR':            0x07c, 
 | 
    'TASK_SWITCH':    0x07d, 
 | 
    'FERR_FREEZE':    0x07e, 
 | 
    'SHUTDOWN':       0x07f, 
 | 
    'VMRUN':          0x080, 
 | 
    'VMMCALL':        0x081, 
 | 
    'VMLOAD':         0x082, 
 | 
    'VMSAVE':         0x083, 
 | 
    'STGI':           0x084, 
 | 
    'CLGI':           0x085, 
 | 
    'SKINIT':         0x086, 
 | 
    'RDTSCP':         0x087, 
 | 
    'ICEBP':          0x088, 
 | 
    'WBINVD':         0x089, 
 | 
    'MONITOR':        0x08a, 
 | 
    'MWAIT':          0x08b, 
 | 
    'MWAIT_COND':     0x08c, 
 | 
    'XSETBV':         0x08d, 
 | 
    'NPF':            0x400, 
 | 
} 
 | 
  
 | 
# EC definition of HSR (from arch/arm64/include/asm/kvm_arm.h) 
 | 
AARCH64_EXIT_REASONS = { 
 | 
    'UNKNOWN':      0x00, 
 | 
    'WFI':          0x01, 
 | 
    'CP15_32':      0x03, 
 | 
    'CP15_64':      0x04, 
 | 
    'CP14_MR':      0x05, 
 | 
    'CP14_LS':      0x06, 
 | 
    'FP_ASIMD':     0x07, 
 | 
    'CP10_ID':      0x08, 
 | 
    'CP14_64':      0x0C, 
 | 
    'ILL_ISS':      0x0E, 
 | 
    'SVC32':        0x11, 
 | 
    'HVC32':        0x12, 
 | 
    'SMC32':        0x13, 
 | 
    'SVC64':        0x15, 
 | 
    'HVC64':        0x16, 
 | 
    'SMC64':        0x17, 
 | 
    'SYS64':        0x18, 
 | 
    'IABT':         0x20, 
 | 
    'IABT_HYP':     0x21, 
 | 
    'PC_ALIGN':     0x22, 
 | 
    'DABT':         0x24, 
 | 
    'DABT_HYP':     0x25, 
 | 
    'SP_ALIGN':     0x26, 
 | 
    'FP_EXC32':     0x28, 
 | 
    'FP_EXC64':     0x2C, 
 | 
    'SERROR':       0x2F, 
 | 
    'BREAKPT':      0x30, 
 | 
    'BREAKPT_HYP':  0x31, 
 | 
    'SOFTSTP':      0x32, 
 | 
    'SOFTSTP_HYP':  0x33, 
 | 
    'WATCHPT':      0x34, 
 | 
    'WATCHPT_HYP':  0x35, 
 | 
    'BKPT32':       0x38, 
 | 
    'VECTOR32':     0x3A, 
 | 
    'BRK64':        0x3C, 
 | 
} 
 | 
  
 | 
# From include/uapi/linux/kvm.h, KVM_EXIT_xxx 
 | 
USERSPACE_EXIT_REASONS = { 
 | 
    'UNKNOWN':          0, 
 | 
    'EXCEPTION':        1, 
 | 
    'IO':               2, 
 | 
    'HYPERCALL':        3, 
 | 
    'DEBUG':            4, 
 | 
    'HLT':              5, 
 | 
    'MMIO':             6, 
 | 
    'IRQ_WINDOW_OPEN':  7, 
 | 
    'SHUTDOWN':         8, 
 | 
    'FAIL_ENTRY':       9, 
 | 
    'INTR':             10, 
 | 
    'SET_TPR':          11, 
 | 
    'TPR_ACCESS':       12, 
 | 
    'S390_SIEIC':       13, 
 | 
    'S390_RESET':       14, 
 | 
    'DCR':              15, 
 | 
    'NMI':              16, 
 | 
    'INTERNAL_ERROR':   17, 
 | 
    'OSI':              18, 
 | 
    'PAPR_HCALL':       19, 
 | 
    'S390_UCONTROL':    20, 
 | 
    'WATCHDOG':         21, 
 | 
    'S390_TSCH':        22, 
 | 
    'EPR':              23, 
 | 
    'SYSTEM_EVENT':     24, 
 | 
} 
 | 
  
 | 
IOCTL_NUMBERS = { 
 | 
    'SET_FILTER':  0x40082406, 
 | 
    'ENABLE':      0x00002400, 
 | 
    'DISABLE':     0x00002401, 
 | 
    'RESET':       0x00002403, 
 | 
} 
 | 
  
 | 
ENCODING = locale.getpreferredencoding(False) 
 | 
TRACE_FILTER = re.compile(r'^[^\(]*$') 
 | 
  
 | 
  
 | 
class Arch(object): 
 | 
    """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 
 | 
    def get_arch(): 
 | 
        machine = os.uname()[4] 
 | 
  
 | 
        if machine.startswith('ppc'): 
 | 
            return ArchPPC() 
 | 
        elif machine.startswith('aarch64'): 
 | 
            return ArchA64() 
 | 
        elif machine.startswith('s390'): 
 | 
            return ArchS390() 
 | 
        else: 
 | 
            # X86_64 
 | 
            for line in open('/proc/cpuinfo'): 
 | 
                if not line.startswith('flags'): 
 | 
                    continue 
 | 
  
 | 
                flags = line.split() 
 | 
                if 'vmx' in flags: 
 | 
                    return ArchX86(VMX_EXIT_REASONS) 
 | 
                if 'svm' in flags: 
 | 
                    return ArchX86(SVM_EXIT_REASONS) 
 | 
                return 
 | 
  
 | 
    def tracepoint_is_child(self, field): 
 | 
        if (TRACE_FILTER.match(field)): 
 | 
            return None 
 | 
        return field.split('(', 1)[0] 
 | 
  
 | 
  
 | 
class ArchX86(Arch): 
 | 
    def __init__(self, exit_reasons): 
 | 
        self.sc_perf_evt_open = 298 
 | 
        self.ioctl_numbers = IOCTL_NUMBERS 
 | 
        self.exit_reason_field = 'exit_reason' 
 | 
        self.exit_reasons = exit_reasons 
 | 
  
 | 
    def debugfs_is_child(self, field): 
 | 
        """ Returns name of parent if 'field' is a child, None otherwise """ 
 | 
        return None 
 | 
  
 | 
  
 | 
class ArchPPC(Arch): 
 | 
    def __init__(self): 
 | 
        self.sc_perf_evt_open = 319 
 | 
        self.ioctl_numbers = IOCTL_NUMBERS 
 | 
        self.ioctl_numbers['ENABLE'] = 0x20002400 
 | 
        self.ioctl_numbers['DISABLE'] = 0x20002401 
 | 
        self.ioctl_numbers['RESET'] = 0x20002403 
 | 
  
 | 
        # PPC comes in 32 and 64 bit and some generated ioctl 
 | 
        # numbers depend on the wordsize. 
 | 
        char_ptr_size = ctypes.sizeof(ctypes.c_char_p) 
 | 
        self.ioctl_numbers['SET_FILTER'] = 0x80002406 | char_ptr_size << 16 
 | 
        self.exit_reason_field = 'exit_nr' 
 | 
        self.exit_reasons = {} 
 | 
  
 | 
    def debugfs_is_child(self, field): 
 | 
        """ Returns name of parent if 'field' is a child, None otherwise """ 
 | 
        return None 
 | 
  
 | 
  
 | 
class ArchA64(Arch): 
 | 
    def __init__(self): 
 | 
        self.sc_perf_evt_open = 241 
 | 
        self.ioctl_numbers = IOCTL_NUMBERS 
 | 
        self.exit_reason_field = 'esr_ec' 
 | 
        self.exit_reasons = AARCH64_EXIT_REASONS 
 | 
  
 | 
    def debugfs_is_child(self, field): 
 | 
        """ Returns name of parent if 'field' is a child, None otherwise """ 
 | 
        return None 
 | 
  
 | 
  
 | 
class ArchS390(Arch): 
 | 
    def __init__(self): 
 | 
        self.sc_perf_evt_open = 331 
 | 
        self.ioctl_numbers = IOCTL_NUMBERS 
 | 
        self.exit_reason_field = None 
 | 
        self.exit_reasons = None 
 | 
  
 | 
    def debugfs_is_child(self, field): 
 | 
        """ Returns name of parent if 'field' is a child, None otherwise """ 
 | 
        if field.startswith('instruction_'): 
 | 
            return 'exit_instruction' 
 | 
  
 | 
  
 | 
ARCH = Arch.get_arch() 
 | 
  
 | 
  
 | 
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), 
 | 
                ('sample_freq', ctypes.c_uint64), 
 | 
                ('sample_type', ctypes.c_uint64), 
 | 
                ('read_format', ctypes.c_uint64), 
 | 
                ('flags', ctypes.c_uint64), 
 | 
                ('wakeup_events', ctypes.c_uint32), 
 | 
                ('bp_type', ctypes.c_uint32), 
 | 
                ('bp_addr', ctypes.c_uint64), 
 | 
                ('bp_len', ctypes.c_uint64), 
 | 
                ] 
 | 
  
 | 
    def __init__(self): 
 | 
        super(self.__class__, self).__init__() 
 | 
        self.type = PERF_TYPE_TRACEPOINT 
 | 
        self.size = ctypes.sizeof(self) 
 | 
        self.read_format = PERF_FORMAT_GROUP 
 | 
  
 | 
  
 | 
PERF_TYPE_TRACEPOINT = 2 
 | 
PERF_FORMAT_GROUP = 1 << 3 
 | 
  
 | 
  
 | 
class Group(object): 
 | 
    """Represents a perf event group.""" 
 | 
  
 | 
    def __init__(self): 
 | 
        self.events = [] 
 | 
  
 | 
    def add_event(self, event): 
 | 
        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], 
 | 
                        struct.unpack(read_format, 
 | 
                                      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.libc = ctypes.CDLL('libc.so.6', use_errno=True) 
 | 
        self.syscall = self.libc.syscall 
 | 
        self.name = name 
 | 
        self.fd = None 
 | 
        self._setup_event(group, trace_cpu, trace_pid, trace_point, 
 | 
                          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 _perf_event_open(self, 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 self.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)) 
 | 
  
 | 
    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') 
 | 
  
 | 
        event_attr = perf_event_attr() 
 | 
        event_attr.config = int(open(id_path).read()) 
 | 
        return event_attr 
 | 
  
 | 
    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 
 | 
  
 | 
        fd = self._perf_event_open(event_attr, trace_pid, 
 | 
                                   trace_cpu, group_leader, 0) 
 | 
        if fd == -1: 
 | 
            err = ctypes.get_errno() 
 | 
            raise OSError(err, os.strerror(err), 
 | 
                          'while calling sys_perf_event_open().') 
 | 
  
 | 
        if trace_filter: 
 | 
            fcntl.ioctl(fd, ARCH.ioctl_numbers['SET_FILTER'], 
 | 
                        trace_filter) 
 | 
  
 | 
        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 Provider(object): 
 | 
    """Encapsulates functionalities used by all providers.""" 
 | 
    def __init__(self, pid): 
 | 
        self.child_events = False 
 | 
        self.pid = pid 
 | 
  
 | 
    @staticmethod 
 | 
    def is_field_wanted(fields_filter, field): 
 | 
        """Indicate whether field is valid according to fields_filter.""" 
 | 
        if not fields_filter: 
 | 
            return True 
 | 
        return re.match(fields_filter, field) is not None 
 | 
  
 | 
    @staticmethod 
 | 
    def walkdir(path): 
 | 
        """Returns os.walk() data for specified directory. 
 | 
  
 | 
        As it is only a wrapper it returns the same 3-tuple of (dirpath, 
 | 
        dirnames, filenames). 
 | 
        """ 
 | 
        return next(os.walk(path)) 
 | 
  
 | 
  
 | 
class TracepointProvider(Provider): 
 | 
    """Data provider for the stats class. 
 | 
  
 | 
    Manages the events/groups from which it acquires its data. 
 | 
  
 | 
    """ 
 | 
    def __init__(self, pid, fields_filter): 
 | 
        self.group_leaders = [] 
 | 
        self.filters = self._get_filters() 
 | 
        self.update_fields(fields_filter) 
 | 
        super(TracepointProvider, self).__init__(pid) 
 | 
  
 | 
    @staticmethod 
 | 
    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_reason_field and ARCH.exit_reasons: 
 | 
            filters['kvm_exit'] = (ARCH.exit_reason_field, ARCH.exit_reasons) 
 | 
        return filters 
 | 
  
 | 
    def _get_available_fields(self): 
 | 
        """Returns a list of available events 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 = self.walkdir(path)[1] 
 | 
        extra = [] 
 | 
        for field in fields: 
 | 
            if field in self.filters: 
 | 
                filter_name_, filter_dicts = self.filters[field] 
 | 
                for name in filter_dicts: 
 | 
                    extra.append(field + '(' + name + ')') 
 | 
        fields += extra 
 | 
        return fields 
 | 
  
 | 
    def update_fields(self, fields_filter): 
 | 
        """Refresh fields, applying fields_filter""" 
 | 
        self.fields = [field for field in self._get_available_fields() 
 | 
                       if self.is_field_wanted(fields_filter, field)] 
 | 
        # add parents for child fields - otherwise we won't see any output! 
 | 
        for field in self._fields: 
 | 
            parent = ARCH.tracepoint_is_child(field) 
 | 
            if (parent and parent not in self._fields): 
 | 
                self.fields.append(parent) 
 | 
  
 | 
    @staticmethod 
 | 
    def _get_online_cpus(): 
 | 
        """Returns a list of cpu id integers.""" 
 | 
        def parse_int_list(list_string): 
 | 
            """Returns an int list from a string of comma separated integers and 
 | 
            integer ranges.""" 
 | 
            integers = [] 
 | 
            members = list_string.split(',') 
 | 
  
 | 
            for member in members: 
 | 
                if '-' not in member: 
 | 
                    integers.append(int(member)) 
 | 
                else: 
 | 
                    int_range = member.split('-') 
 | 
                    integers.extend(range(int(int_range[0]), 
 | 
                                          int(int_range[1]) + 1)) 
 | 
  
 | 
            return integers 
 | 
  
 | 
        with open('/sys/devices/system/cpu/online') as cpu_list: 
 | 
            cpu_string = cpu_list.readline() 
 | 
            return parse_int_list(cpu_string) 
 | 
  
 | 
    def _setup_traces(self): 
 | 
        """Creates all event and group objects needed to be able to retrieve 
 | 
        data.""" 
 | 
        fields = self._get_available_fields() 
 | 
        if self._pid > 0: 
 | 
            # Fetch list of all threads of the monitored pid, as qemu 
 | 
            # starts a thread for each vcpu. 
 | 
            path = os.path.join('/proc', str(self._pid), 'task') 
 | 
            groupids = self.walkdir(path)[1] 
 | 
        else: 
 | 
            groupids = self._get_online_cpus() 
 | 
  
 | 
        # The constant is needed as a buffer for python libs, std 
 | 
        # streams and other files that the script opens. 
 | 
        newlim = len(groupids) * len(fields) + 50 
 | 
        try: 
 | 
            softlim_, hardlim = resource.getrlimit(resource.RLIMIT_NOFILE) 
 | 
  
 | 
            if hardlim < newlim: 
 | 
                # Now we need CAP_SYS_RESOURCE, to increase the hard limit. 
 | 
                resource.setrlimit(resource.RLIMIT_NOFILE, (newlim, newlim)) 
 | 
            else: 
 | 
                # Raising the soft limit is sufficient. 
 | 
                resource.setrlimit(resource.RLIMIT_NOFILE, (newlim, hardlim)) 
 | 
  
 | 
        except ValueError: 
 | 
            sys.exit("NOFILE rlimit could not be raised to {0}".format(newlim)) 
 | 
  
 | 
        for groupid in groupids: 
 | 
            group = Group() 
 | 
            for name in fields: 
 | 
                tracepoint = name 
 | 
                tracefilter = None 
 | 
                match = re.match(r'(.*)\((.*)\)', name) 
 | 
                if match: 
 | 
                    tracepoint, sub = match.groups() 
 | 
                    tracefilter = ('%s==%d\0' % 
 | 
                                   (self.filters[tracepoint][0], 
 | 
                                    self.filters[tracepoint][1][sub])) 
 | 
  
 | 
                # From perf_event_open(2): 
 | 
                # pid > 0 and cpu == -1 
 | 
                # This measures the specified process/thread on any CPU. 
 | 
                # 
 | 
                # pid == -1 and cpu >= 0 
 | 
                # This measures all processes/threads on the specified CPU. 
 | 
                trace_cpu = groupid if self._pid == 0 else -1 
 | 
                trace_pid = int(groupid) if self._pid != 0 else -1 
 | 
  
 | 
                group.add_event(Event(name=name, 
 | 
                                      group=group, 
 | 
                                      trace_cpu=trace_cpu, 
 | 
                                      trace_pid=trace_pid, 
 | 
                                      trace_point=tracepoint, 
 | 
                                      trace_filter=tracefilter)) 
 | 
  
 | 
            self.group_leaders.append(group) 
 | 
  
 | 
    @property 
 | 
    def fields(self): 
 | 
        return self._fields 
 | 
  
 | 
    @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): 
 | 
                if event.name in fields: 
 | 
                    event.reset() 
 | 
                    event.enable() 
 | 
                else: 
 | 
                    # Do not disable the group leader. 
 | 
                    # It would disable all of its events. 
 | 
                    if index != 0: 
 | 
                        event.disable() 
 | 
  
 | 
    @property 
 | 
    def pid(self): 
 | 
        return self._pid 
 | 
  
 | 
    @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, by_guest=0): 
 | 
        """Returns 'event name: current value' for all enabled events.""" 
 | 
        ret = defaultdict(int) 
 | 
        for group in self.group_leaders: 
 | 
            for name, val in group.read().items(): 
 | 
                if name not in self._fields: 
 | 
                    continue 
 | 
                parent = ARCH.tracepoint_is_child(name) 
 | 
                if parent: 
 | 
                    name += ' ' + parent 
 | 
                ret[name] += val 
 | 
        return ret 
 | 
  
 | 
    def reset(self): 
 | 
        """Reset all field counters""" 
 | 
        for group in self.group_leaders: 
 | 
            for event in group.events: 
 | 
                event.reset() 
 | 
  
 | 
  
 | 
class DebugfsProvider(Provider): 
 | 
    """Provides data from the files that KVM creates in the kvm debugfs 
 | 
    folder.""" 
 | 
    def __init__(self, pid, fields_filter, include_past): 
 | 
        self.update_fields(fields_filter) 
 | 
        self._baseline = {} 
 | 
        self.do_read = True 
 | 
        self.paths = [] 
 | 
        super(DebugfsProvider, self).__init__(pid) 
 | 
        if include_past: 
 | 
            self._restore() 
 | 
  
 | 
    def _get_available_fields(self): 
 | 
        """"Returns a list of available fields. 
 | 
  
 | 
        The fields are all available KVM debugfs files 
 | 
  
 | 
        """ 
 | 
        return self.walkdir(PATH_DEBUGFS_KVM)[2] 
 | 
  
 | 
    def update_fields(self, fields_filter): 
 | 
        """Refresh fields, applying fields_filter""" 
 | 
        self._fields = [field for field in self._get_available_fields() 
 | 
                        if self.is_field_wanted(fields_filter, field)] 
 | 
        # add parents for child fields - otherwise we won't see any output! 
 | 
        for field in self._fields: 
 | 
            parent = ARCH.debugfs_is_child(field) 
 | 
            if (parent and parent not in self._fields): 
 | 
                self.fields.append(parent) 
 | 
  
 | 
    @property 
 | 
    def fields(self): 
 | 
        return self._fields 
 | 
  
 | 
    @fields.setter 
 | 
    def fields(self, fields): 
 | 
        self._fields = fields 
 | 
        self.reset() 
 | 
  
 | 
    @property 
 | 
    def pid(self): 
 | 
        return self._pid 
 | 
  
 | 
    @pid.setter 
 | 
    def pid(self, pid): 
 | 
        self._pid = pid 
 | 
        if pid != 0: 
 | 
            vms = self.walkdir(PATH_DEBUGFS_KVM)[1] 
 | 
            if len(vms) == 0: 
 | 
                self.do_read = False 
 | 
  
 | 
            self.paths = list(filter(lambda x: "{}-".format(pid) in x, vms)) 
 | 
  
 | 
        else: 
 | 
            self.paths = [] 
 | 
            self.do_read = True 
 | 
  
 | 
    def _verify_paths(self): 
 | 
        """Remove invalid paths""" 
 | 
        for path in self.paths: 
 | 
            if not os.path.exists(os.path.join(PATH_DEBUGFS_KVM, path)): 
 | 
                self.paths.remove(path) 
 | 
                continue 
 | 
  
 | 
    def read(self, reset=0, by_guest=0): 
 | 
        """Returns a dict with format:'file name / field -> current value'. 
 | 
  
 | 
        Parameter 'reset': 
 | 
          0   plain read 
 | 
          1   reset field counts to 0 
 | 
          2   restore the original field counts 
 | 
  
 | 
        """ 
 | 
        results = {} 
 | 
  
 | 
        # If no debugfs filtering support is available, then don't read. 
 | 
        if not self.do_read: 
 | 
            return results 
 | 
        self._verify_paths() 
 | 
  
 | 
        paths = self.paths 
 | 
        if self._pid == 0: 
 | 
            paths = [] 
 | 
            for entry in os.walk(PATH_DEBUGFS_KVM): 
 | 
                for dir in entry[1]: 
 | 
                    paths.append(dir) 
 | 
        for path in paths: 
 | 
            for field in self._fields: 
 | 
                value = self._read_field(field, path) 
 | 
                key = path + field 
 | 
                if reset == 1: 
 | 
                    self._baseline[key] = value 
 | 
                if reset == 2: 
 | 
                    self._baseline[key] = 0 
 | 
                if self._baseline.get(key, -1) == -1: 
 | 
                    self._baseline[key] = value 
 | 
                parent = ARCH.debugfs_is_child(field) 
 | 
                if parent: 
 | 
                    field = field + ' ' + parent 
 | 
                else: 
 | 
                    if by_guest: 
 | 
                        field = key.split('-')[0]    # set 'field' to 'pid' 
 | 
                increment = value - self._baseline.get(key, 0) 
 | 
                if field in results: 
 | 
                    results[field] += increment 
 | 
                else: 
 | 
                    results[field] = increment 
 | 
  
 | 
        return results 
 | 
  
 | 
    def _read_field(self, field, path): 
 | 
        """Returns the value of a single field from a specific VM.""" 
 | 
        try: 
 | 
            return int(open(os.path.join(PATH_DEBUGFS_KVM, 
 | 
                                         path, 
 | 
                                         field)) 
 | 
                       .read()) 
 | 
        except IOError: 
 | 
            return 0 
 | 
  
 | 
    def reset(self): 
 | 
        """Reset field counters""" 
 | 
        self._baseline = {} 
 | 
        self.read(1) 
 | 
  
 | 
    def _restore(self): 
 | 
        """Reset field counters""" 
 | 
        self._baseline = {} 
 | 
        self.read(2) 
 | 
  
 | 
  
 | 
EventStat = namedtuple('EventStat', ['value', 'delta']) 
 | 
  
 | 
  
 | 
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, options): 
 | 
        self.providers = self._get_providers(options) 
 | 
        self._pid_filter = options.pid 
 | 
        self._fields_filter = options.fields 
 | 
        self.values = {} 
 | 
        self._child_events = False 
 | 
  
 | 
    def _get_providers(self, options): 
 | 
        """Returns a list of data providers depending on the passed options.""" 
 | 
        providers = [] 
 | 
  
 | 
        if options.debugfs: 
 | 
            providers.append(DebugfsProvider(options.pid, options.fields, 
 | 
                                             options.dbgfs_include_past)) 
 | 
        if options.tracepoints or not providers: 
 | 
            providers.append(TracepointProvider(options.pid, options.fields)) 
 | 
  
 | 
        return providers 
 | 
  
 | 
    def _update_provider_filters(self): 
 | 
        """Propagates fields filters to providers.""" 
 | 
        # As we reset the counters when updating the fields we can 
 | 
        # also clear the cache of old values. 
 | 
        self.values = {} 
 | 
        for provider in self.providers: 
 | 
            provider.update_fields(self._fields_filter) 
 | 
  
 | 
    def reset(self): 
 | 
        self.values = {} 
 | 
        for provider in self.providers: 
 | 
            provider.reset() 
 | 
  
 | 
    @property 
 | 
    def fields_filter(self): 
 | 
        return self._fields_filter 
 | 
  
 | 
    @fields_filter.setter 
 | 
    def fields_filter(self, fields_filter): 
 | 
        if fields_filter != self._fields_filter: 
 | 
            self._fields_filter = fields_filter 
 | 
            self._update_provider_filters() 
 | 
  
 | 
    @property 
 | 
    def pid_filter(self): 
 | 
        return self._pid_filter 
 | 
  
 | 
    @pid_filter.setter 
 | 
    def pid_filter(self, pid): 
 | 
        if pid != self._pid_filter: 
 | 
            self._pid_filter = pid 
 | 
            self.values = {} 
 | 
            for provider in self.providers: 
 | 
                provider.pid = self._pid_filter 
 | 
  
 | 
    @property 
 | 
    def child_events(self): 
 | 
        return self._child_events 
 | 
  
 | 
    @child_events.setter 
 | 
    def child_events(self, val): 
 | 
        self._child_events = val 
 | 
        for provider in self.providers: 
 | 
            provider.child_events = val 
 | 
  
 | 
    def get(self, by_guest=0): 
 | 
        """Returns a dict with field -> (value, delta to last value) of all 
 | 
        provider data. 
 | 
        Key formats: 
 | 
          * plain: 'key' is event name 
 | 
          * child-parent: 'key' is in format '<child> <parent>' 
 | 
          * pid: 'key' is the pid of the guest, and the record contains the 
 | 
               aggregated event data 
 | 
        These formats are generated by the providers, and handled in class TUI. 
 | 
        """ 
 | 
        for provider in self.providers: 
 | 
            new = provider.read(by_guest=by_guest) 
 | 
            for key in new: 
 | 
                oldval = self.values.get(key, EventStat(0, 0)).value 
 | 
                newval = new.get(key, 0) 
 | 
                newdelta = newval - oldval 
 | 
                self.values[key] = EventStat(newval, newdelta) 
 | 
        return self.values 
 | 
  
 | 
    def toggle_display_guests(self, to_pid): 
 | 
        """Toggle between collection of stats by individual event and by 
 | 
        guest pid 
 | 
  
 | 
        Events reported by DebugfsProvider change when switching to/from 
 | 
        reading by guest values. Hence we have to remove the excess event 
 | 
        names from self.values. 
 | 
  
 | 
        """ 
 | 
        if any(isinstance(ins, TracepointProvider) for ins in self.providers): 
 | 
            return 1 
 | 
        if to_pid: 
 | 
            for provider in self.providers: 
 | 
                if isinstance(provider, DebugfsProvider): 
 | 
                    for key in provider.fields: 
 | 
                        if key in self.values.keys(): 
 | 
                            del self.values[key] 
 | 
        else: 
 | 
            oldvals = self.values.copy() 
 | 
            for key in oldvals: 
 | 
                if key.isdigit(): 
 | 
                    del self.values[key] 
 | 
        # Update oldval (see get()) 
 | 
        self.get(to_pid) 
 | 
        return 0 
 | 
  
 | 
  
 | 
DELAY_DEFAULT = 3.0 
 | 
MAX_GUEST_NAME_LEN = 48 
 | 
MAX_REGEX_LEN = 44 
 | 
SORT_DEFAULT = 0 
 | 
  
 | 
  
 | 
class Tui(object): 
 | 
    """Instruments curses to draw a nice text ui.""" 
 | 
    def __init__(self, stats): 
 | 
        self.stats = stats 
 | 
        self.screen = None 
 | 
        self._delay_initial = 0.25 
 | 
        self._delay_regular = DELAY_DEFAULT 
 | 
        self._sorting = SORT_DEFAULT 
 | 
        self._display_guests = 0 
 | 
  
 | 
    def __enter__(self): 
 | 
        """Initialises curses for later use.  Based on curses.wrapper 
 | 
           implementation from the Python standard library.""" 
 | 
        self.screen = curses.initscr() 
 | 
        curses.noecho() 
 | 
        curses.cbreak() 
 | 
  
 | 
        # The try/catch works around a minor bit of 
 | 
        # over-conscientiousness in the curses module, the error 
 | 
        # return from C start_color() is ignorable. 
 | 
        try: 
 | 
            curses.start_color() 
 | 
        except curses.error: 
 | 
            pass 
 | 
  
 | 
        # Hide cursor in extra statement as some monochrome terminals 
 | 
        # might support hiding but not colors. 
 | 
        try: 
 | 
            curses.curs_set(0) 
 | 
        except curses.error: 
 | 
            pass 
 | 
  
 | 
        curses.use_default_colors() 
 | 
        return self 
 | 
  
 | 
    def __exit__(self, *exception): 
 | 
        """Resets the terminal to its normal state.  Based on curses.wrapper 
 | 
           implementation from the Python standard library.""" 
 | 
        if self.screen: 
 | 
            self.screen.keypad(0) 
 | 
            curses.echo() 
 | 
            curses.nocbreak() 
 | 
            curses.endwin() 
 | 
  
 | 
    @staticmethod 
 | 
    def get_all_gnames(): 
 | 
        """Returns a list of (pid, gname) tuples of all running guests""" 
 | 
        res = [] 
 | 
        try: 
 | 
            child = subprocess.Popen(['ps', '-A', '--format', 'pid,args'], 
 | 
                                     stdout=subprocess.PIPE) 
 | 
        except: 
 | 
            raise Exception 
 | 
        for line in child.stdout: 
 | 
            line = line.decode(ENCODING).lstrip().split(' ', 1) 
 | 
            # perform a sanity check before calling the more expensive 
 | 
            # function to possibly extract the guest name 
 | 
            if ' -name ' in line[1]: 
 | 
                res.append((line[0], Tui.get_gname_from_pid(line[0]))) 
 | 
        child.stdout.close() 
 | 
  
 | 
        return res 
 | 
  
 | 
    def _print_all_gnames(self, row): 
 | 
        """Print a list of all running guests along with their pids.""" 
 | 
        self.screen.addstr(row, 2, '%8s  %-60s' % 
 | 
                           ('Pid', 'Guest Name (fuzzy list, might be ' 
 | 
                            'inaccurate!)'), 
 | 
                           curses.A_UNDERLINE) 
 | 
        row += 1 
 | 
        try: 
 | 
            for line in self.get_all_gnames(): 
 | 
                self.screen.addstr(row, 2, '%8s  %-60s' % (line[0], line[1])) 
 | 
                row += 1 
 | 
                if row >= self.screen.getmaxyx()[0]: 
 | 
                    break 
 | 
        except Exception: 
 | 
            self.screen.addstr(row + 1, 2, 'Not available') 
 | 
  
 | 
    @staticmethod 
 | 
    def get_pid_from_gname(gname): 
 | 
        """Fuzzy function to convert guest name to QEMU process pid. 
 | 
  
 | 
        Returns a list of potential pids, can be empty if no match found. 
 | 
        Throws an exception on processing errors. 
 | 
  
 | 
        """ 
 | 
        pids = [] 
 | 
        for line in Tui.get_all_gnames(): 
 | 
            if gname == line[1]: 
 | 
                pids.append(int(line[0])) 
 | 
  
 | 
        return pids 
 | 
  
 | 
    @staticmethod 
 | 
    def get_gname_from_pid(pid): 
 | 
        """Returns the guest name for a QEMU process pid. 
 | 
  
 | 
        Extracts the guest name from the QEMU comma line by processing the 
 | 
        '-name' option. Will also handle names specified out of sequence. 
 | 
  
 | 
        """ 
 | 
        name = '' 
 | 
        try: 
 | 
            line = open('/proc/{}/cmdline' 
 | 
                        .format(pid), 'r').read().split('\0') 
 | 
            parms = line[line.index('-name') + 1].split(',') 
 | 
            while '' in parms: 
 | 
                # commas are escaped (i.e. ',,'), hence e.g. 'foo,bar' results 
 | 
                # in # ['foo', '', 'bar'], which we revert here 
 | 
                idx = parms.index('') 
 | 
                parms[idx - 1] += ',' + parms[idx + 1] 
 | 
                del parms[idx:idx+2] 
 | 
            # the '-name' switch allows for two ways to specify the guest name, 
 | 
            # where the plain name overrides the name specified via 'guest=' 
 | 
            for arg in parms: 
 | 
                if '=' not in arg: 
 | 
                    name = arg 
 | 
                    break 
 | 
                if arg[:6] == 'guest=': 
 | 
                    name = arg[6:] 
 | 
        except (ValueError, IOError, IndexError): 
 | 
            pass 
 | 
  
 | 
        return name 
 | 
  
 | 
    def _update_pid(self, pid): 
 | 
        """Propagates pid selection to stats object.""" 
 | 
        self.screen.addstr(4, 1, 'Updating pid filter...') 
 | 
        self.screen.refresh() 
 | 
        self.stats.pid_filter = pid 
 | 
  
 | 
    def _refresh_header(self, pid=None): 
 | 
        """Refreshes the header.""" 
 | 
        if pid is None: 
 | 
            pid = self.stats.pid_filter 
 | 
        self.screen.erase() 
 | 
        gname = self.get_gname_from_pid(pid) 
 | 
        self._gname = gname 
 | 
        if gname: 
 | 
            gname = ('({})'.format(gname[:MAX_GUEST_NAME_LEN] + '...' 
 | 
                                   if len(gname) > MAX_GUEST_NAME_LEN 
 | 
                                   else gname)) 
 | 
        if pid > 0: 
 | 
            self._headline = 'kvm statistics - pid {0} {1}'.format(pid, gname) 
 | 
        else: 
 | 
            self._headline = 'kvm statistics - summary' 
 | 
        self.screen.addstr(0, 0, self._headline, curses.A_BOLD) 
 | 
        if self.stats.fields_filter: 
 | 
            regex = self.stats.fields_filter 
 | 
            if len(regex) > MAX_REGEX_LEN: 
 | 
                regex = regex[:MAX_REGEX_LEN] + '...' 
 | 
            self.screen.addstr(1, 17, 'regex filter: {0}'.format(regex)) 
 | 
        if self._display_guests: 
 | 
            col_name = 'Guest Name' 
 | 
        else: 
 | 
            col_name = 'Event' 
 | 
        self.screen.addstr(2, 1, '%-40s %10s%7s %8s' % 
 | 
                           (col_name, 'Total', '%Total', 'CurAvg/s'), 
 | 
                           curses.A_STANDOUT) 
 | 
        self.screen.addstr(4, 1, 'Collecting data...') 
 | 
        self.screen.refresh() 
 | 
  
 | 
    def _refresh_body(self, sleeptime): 
 | 
        def insert_child(sorted_items, child, values, parent): 
 | 
            num = len(sorted_items) 
 | 
            for i in range(0, num): 
 | 
                # only add child if parent is present 
 | 
                if parent.startswith(sorted_items[i][0]): 
 | 
                    sorted_items.insert(i + 1, ('  ' + child, values)) 
 | 
  
 | 
        def get_sorted_events(self, stats): 
 | 
            """ separate parent and child events """ 
 | 
            if self._sorting == SORT_DEFAULT: 
 | 
                def sortkey(pair): 
 | 
                    # sort by (delta value, overall value) 
 | 
                    v = pair[1] 
 | 
                    return (v.delta, v.value) 
 | 
            else: 
 | 
                def sortkey(pair): 
 | 
                    # sort by overall value 
 | 
                    v = pair[1] 
 | 
                    return v.value 
 | 
  
 | 
            childs = [] 
 | 
            sorted_items = [] 
 | 
            # we can't rule out child events to appear prior to parents even 
 | 
            # when sorted - separate out all children first, and add in later 
 | 
            for key, values in sorted(stats.items(), key=sortkey, 
 | 
                                      reverse=True): 
 | 
                if values == (0, 0): 
 | 
                    continue 
 | 
                if key.find(' ') != -1: 
 | 
                    if not self.stats.child_events: 
 | 
                        continue 
 | 
                    childs.insert(0, (key, values)) 
 | 
                else: 
 | 
                    sorted_items.append((key, values)) 
 | 
            if self.stats.child_events: 
 | 
                for key, values in childs: 
 | 
                    (child, parent) = key.split(' ') 
 | 
                    insert_child(sorted_items, child, values, parent) 
 | 
  
 | 
            return sorted_items 
 | 
  
 | 
        if not self._is_running_guest(self.stats.pid_filter): 
 | 
            if self._gname: 
 | 
                try: # ...to identify the guest by name in case it's back 
 | 
                    pids = self.get_pid_from_gname(self._gname) 
 | 
                    if len(pids) == 1: 
 | 
                        self._refresh_header(pids[0]) 
 | 
                        self._update_pid(pids[0]) 
 | 
                        return 
 | 
                except: 
 | 
                    pass 
 | 
            self._display_guest_dead() 
 | 
            # leave final data on screen 
 | 
            return 
 | 
        row = 3 
 | 
        self.screen.move(row, 0) 
 | 
        self.screen.clrtobot() 
 | 
        stats = self.stats.get(self._display_guests) 
 | 
        total = 0. 
 | 
        ctotal = 0. 
 | 
        for key, values in stats.items(): 
 | 
            if self._display_guests: 
 | 
                if self.get_gname_from_pid(key): 
 | 
                    total += values.value 
 | 
                continue 
 | 
            if not key.find(' ') != -1: 
 | 
                total += values.value 
 | 
            else: 
 | 
                ctotal += values.value 
 | 
        if total == 0.: 
 | 
            # we don't have any fields, or all non-child events are filtered 
 | 
            total = ctotal 
 | 
  
 | 
        # print events 
 | 
        tavg = 0 
 | 
        tcur = 0 
 | 
        guest_removed = False 
 | 
        for key, values in get_sorted_events(self, stats): 
 | 
            if row >= self.screen.getmaxyx()[0] - 1 or values == (0, 0): 
 | 
                break 
 | 
            if self._display_guests: 
 | 
                key = self.get_gname_from_pid(key) 
 | 
                if not key: 
 | 
                    continue 
 | 
            cur = int(round(values.delta / sleeptime)) if values.delta else 0 
 | 
            if cur < 0: 
 | 
                guest_removed = True 
 | 
                continue 
 | 
            if key[0] != ' ': 
 | 
                if values.delta: 
 | 
                    tcur += values.delta 
 | 
                ptotal = values.value 
 | 
                ltotal = total 
 | 
            else: 
 | 
                ltotal = ptotal 
 | 
            self.screen.addstr(row, 1, '%-40s %10d%7.1f %8s' % (key, 
 | 
                               values.value, 
 | 
                               values.value * 100 / float(ltotal), cur)) 
 | 
            row += 1 
 | 
        if row == 3: 
 | 
            if guest_removed: 
 | 
                self.screen.addstr(4, 1, 'Guest removed, updating...') 
 | 
            else: 
 | 
                self.screen.addstr(4, 1, 'No matching events reported yet') 
 | 
        if row > 4: 
 | 
            tavg = int(round(tcur / sleeptime)) if tcur > 0 else '' 
 | 
            self.screen.addstr(row, 1, '%-40s %10d        %8s' % 
 | 
                               ('Total', total, tavg), curses.A_BOLD) 
 | 
        self.screen.refresh() 
 | 
  
 | 
    def _display_guest_dead(self): 
 | 
        marker = '   Guest is DEAD   ' 
 | 
        y = min(len(self._headline), 80 - len(marker)) 
 | 
        self.screen.addstr(0, y, marker, curses.A_BLINK | curses.A_STANDOUT) 
 | 
  
 | 
    def _show_msg(self, text): 
 | 
        """Display message centered text and exit on key press""" 
 | 
        hint = 'Press any key to continue' 
 | 
        curses.cbreak() 
 | 
        self.screen.erase() 
 | 
        (x, term_width) = self.screen.getmaxyx() 
 | 
        row = 2 
 | 
        for line in text: 
 | 
            start = (term_width - len(line)) // 2 
 | 
            self.screen.addstr(row, start, line) 
 | 
            row += 1 
 | 
        self.screen.addstr(row + 1, (term_width - len(hint)) // 2, hint, 
 | 
                           curses.A_STANDOUT) 
 | 
        self.screen.getkey() 
 | 
  
 | 
    def _show_help_interactive(self): 
 | 
        """Display help with list of interactive commands""" 
 | 
        msg = ('   b     toggle events by guests (debugfs only, honors' 
 | 
               ' filters)', 
 | 
               '   c     clear filter', 
 | 
               '   f     filter by regular expression', 
 | 
               '   g     filter by guest name/PID', 
 | 
               '   h     display interactive commands reference', 
 | 
               '   o     toggle sorting order (Total vs CurAvg/s)', 
 | 
               '   p     filter by guest name/PID', 
 | 
               '   q     quit', 
 | 
               '   r     reset stats', 
 | 
               '   s     set update interval', 
 | 
               '   x     toggle reporting of stats for individual child trace' 
 | 
               ' events', 
 | 
               'Any other key refreshes statistics immediately') 
 | 
        curses.cbreak() 
 | 
        self.screen.erase() 
 | 
        self.screen.addstr(0, 0, "Interactive commands reference", 
 | 
                           curses.A_BOLD) 
 | 
        self.screen.addstr(2, 0, "Press any key to exit", curses.A_STANDOUT) 
 | 
        row = 4 
 | 
        for line in msg: 
 | 
            self.screen.addstr(row, 0, line) 
 | 
            row += 1 
 | 
        self.screen.getkey() 
 | 
        self._refresh_header() 
 | 
  
 | 
    def _show_filter_selection(self): 
 | 
        """Draws filter selection mask. 
 | 
  
 | 
        Asks for a valid regex and sets the fields filter accordingly. 
 | 
  
 | 
        """ 
 | 
        msg = '' 
 | 
        while True: 
 | 
            self.screen.erase() 
 | 
            self.screen.addstr(0, 0, 
 | 
                               "Show statistics for events matching a regex.", 
 | 
                               curses.A_BOLD) 
 | 
            self.screen.addstr(2, 0, 
 | 
                               "Current regex: {0}" 
 | 
                               .format(self.stats.fields_filter)) 
 | 
            self.screen.addstr(5, 0, msg) 
 | 
            self.screen.addstr(3, 0, "New regex: ") 
 | 
            curses.echo() 
 | 
            regex = self.screen.getstr().decode(ENCODING) 
 | 
            curses.noecho() 
 | 
            if len(regex) == 0: 
 | 
                self.stats.fields_filter = '' 
 | 
                self._refresh_header() 
 | 
                return 
 | 
            try: 
 | 
                re.compile(regex) 
 | 
                self.stats.fields_filter = regex 
 | 
                self._refresh_header() 
 | 
                return 
 | 
            except re.error: 
 | 
                msg = '"' + regex + '": Not a valid regular expression' 
 | 
                continue 
 | 
  
 | 
    def _show_set_update_interval(self): 
 | 
        """Draws update interval selection mask.""" 
 | 
        msg = '' 
 | 
        while True: 
 | 
            self.screen.erase() 
 | 
            self.screen.addstr(0, 0, 'Set update interval (defaults to %.1fs).' % 
 | 
                               DELAY_DEFAULT, curses.A_BOLD) 
 | 
            self.screen.addstr(4, 0, msg) 
 | 
            self.screen.addstr(2, 0, 'Change delay from %.1fs to ' % 
 | 
                               self._delay_regular) 
 | 
            curses.echo() 
 | 
            val = self.screen.getstr().decode(ENCODING) 
 | 
            curses.noecho() 
 | 
  
 | 
            try: 
 | 
                if len(val) > 0: 
 | 
                    delay = float(val) 
 | 
                    if delay < 0.1: 
 | 
                        msg = '"' + str(val) + '": Value must be >=0.1' 
 | 
                        continue 
 | 
                    if delay > 25.5: 
 | 
                        msg = '"' + str(val) + '": Value must be <=25.5' 
 | 
                        continue 
 | 
                else: 
 | 
                    delay = DELAY_DEFAULT 
 | 
                self._delay_regular = delay 
 | 
                break 
 | 
  
 | 
            except ValueError: 
 | 
                msg = '"' + str(val) + '": Invalid value' 
 | 
        self._refresh_header() 
 | 
  
 | 
    def _is_running_guest(self, pid): 
 | 
        """Check if pid is still a running process.""" 
 | 
        if not pid: 
 | 
            return True 
 | 
        return os.path.isdir(os.path.join('/proc/', str(pid))) 
 | 
  
 | 
    def _show_vm_selection_by_guest(self): 
 | 
        """Draws guest selection mask. 
 | 
  
 | 
        Asks for a guest name or pid until a valid guest name or '' is entered. 
 | 
  
 | 
        """ 
 | 
        msg = '' 
 | 
        while True: 
 | 
            self.screen.erase() 
 | 
            self.screen.addstr(0, 0, 
 | 
                               'Show statistics for specific guest or pid.', 
 | 
                               curses.A_BOLD) 
 | 
            self.screen.addstr(1, 0, 
 | 
                               'This might limit the shown data to the trace ' 
 | 
                               'statistics.') 
 | 
            self.screen.addstr(5, 0, msg) 
 | 
            self._print_all_gnames(7) 
 | 
            curses.echo() 
 | 
            curses.curs_set(1) 
 | 
            self.screen.addstr(3, 0, "Guest or pid [ENTER exits]: ") 
 | 
            guest = self.screen.getstr().decode(ENCODING) 
 | 
            curses.noecho() 
 | 
  
 | 
            pid = 0 
 | 
            if not guest or guest == '0': 
 | 
                break 
 | 
            if guest.isdigit(): 
 | 
                if not self._is_running_guest(guest): 
 | 
                    msg = '"' + guest + '": Not a running process' 
 | 
                    continue 
 | 
                pid = int(guest) 
 | 
                break 
 | 
            pids = [] 
 | 
            try: 
 | 
                pids = self.get_pid_from_gname(guest) 
 | 
            except: 
 | 
                msg = '"' + guest + '": Internal error while searching, ' \ 
 | 
                      'use pid filter instead' 
 | 
                continue 
 | 
            if len(pids) == 0: 
 | 
                msg = '"' + guest + '": Not an active guest' 
 | 
                continue 
 | 
            if len(pids) > 1: 
 | 
                msg = '"' + guest + '": Multiple matches found, use pid ' \ 
 | 
                      'filter instead' 
 | 
                continue 
 | 
            pid = pids[0] 
 | 
            break 
 | 
        curses.curs_set(0) 
 | 
        self._refresh_header(pid) 
 | 
        self._update_pid(pid) 
 | 
  
 | 
    def show_stats(self): 
 | 
        """Refreshes the screen and processes user input.""" 
 | 
        sleeptime = self._delay_initial 
 | 
        self._refresh_header() 
 | 
        start = 0.0  # result based on init value never appears on screen 
 | 
        while True: 
 | 
            self._refresh_body(time.time() - start) 
 | 
            curses.halfdelay(int(sleeptime * 10)) 
 | 
            start = time.time() 
 | 
            sleeptime = self._delay_regular 
 | 
            try: 
 | 
                char = self.screen.getkey() 
 | 
                if char == 'b': 
 | 
                    self._display_guests = not self._display_guests 
 | 
                    if self.stats.toggle_display_guests(self._display_guests): 
 | 
                        self._show_msg(['Command not available with ' 
 | 
                                        'tracepoints enabled', 'Restart with ' 
 | 
                                        'debugfs only (see option \'-d\') and ' 
 | 
                                        'try again!']) 
 | 
                        self._display_guests = not self._display_guests 
 | 
                    self._refresh_header() 
 | 
                if char == 'c': 
 | 
                    self.stats.fields_filter = '' 
 | 
                    self._refresh_header(0) 
 | 
                    self._update_pid(0) 
 | 
                if char == 'f': 
 | 
                    curses.curs_set(1) 
 | 
                    self._show_filter_selection() 
 | 
                    curses.curs_set(0) 
 | 
                    sleeptime = self._delay_initial 
 | 
                if char == 'g' or char == 'p': 
 | 
                    self._show_vm_selection_by_guest() 
 | 
                    sleeptime = self._delay_initial 
 | 
                if char == 'h': 
 | 
                    self._show_help_interactive() 
 | 
                if char == 'o': 
 | 
                    self._sorting = not self._sorting 
 | 
                if char == 'q': 
 | 
                    break 
 | 
                if char == 'r': 
 | 
                    self.stats.reset() 
 | 
                if char == 's': 
 | 
                    curses.curs_set(1) 
 | 
                    self._show_set_update_interval() 
 | 
                    curses.curs_set(0) 
 | 
                    sleeptime = self._delay_initial 
 | 
                if char == 'x': 
 | 
                    self.stats.child_events = not self.stats.child_events 
 | 
            except KeyboardInterrupt: 
 | 
                break 
 | 
            except curses.error: 
 | 
                continue 
 | 
  
 | 
  
 | 
def batch(stats): 
 | 
    """Prints statistics in a key, value format.""" 
 | 
    try: 
 | 
        s = stats.get() 
 | 
        time.sleep(1) 
 | 
        s = stats.get() 
 | 
        for key, values in sorted(s.items()): 
 | 
            print('%-42s%10d%10d' % (key.split(' ')[0], values.value, 
 | 
                  values.delta)) 
 | 
    except KeyboardInterrupt: 
 | 
        pass 
 | 
  
 | 
  
 | 
def log(stats): 
 | 
    """Prints statistics as reiterating key block, multiple value blocks.""" 
 | 
    keys = sorted(stats.get().keys()) 
 | 
  
 | 
    def banner(): 
 | 
        for key in keys: 
 | 
            print(key.split(' ')[0], end=' ') 
 | 
        print() 
 | 
  
 | 
    def statline(): 
 | 
        s = stats.get() 
 | 
        for key in keys: 
 | 
            print(' %9d' % s[key].delta, end=' ') 
 | 
        print() 
 | 
    line = 0 
 | 
    banner_repeat = 20 
 | 
    while True: 
 | 
        try: 
 | 
            time.sleep(1) 
 | 
            if line % banner_repeat == 0: 
 | 
                banner() 
 | 
            statline() 
 | 
            line += 1 
 | 
        except KeyboardInterrupt: 
 | 
            break 
 | 
  
 | 
  
 | 
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 
 | 
currently available perf traces. 
 | 
  
 | 
The monitoring takes additional cpu cycles and might affect the VM's 
 | 
performance. 
 | 
  
 | 
Requirements: 
 | 
- Access to: 
 | 
    %s 
 | 
    %s/events/* 
 | 
    /proc/pid/task 
 | 
- /proc/sys/kernel/perf_event_paranoid < 1 if user has no 
 | 
  CAP_SYS_ADMIN and perf events are used. 
 | 
- CAP_SYS_RESOURCE if the hard limit is not high enough to allow 
 | 
  the large number of files that are possibly opened. 
 | 
  
 | 
Interactive Commands: 
 | 
   b     toggle events by guests (debugfs only, honors filters) 
 | 
   c     clear filter 
 | 
   f     filter by regular expression 
 | 
   g     filter by guest name 
 | 
   h     display interactive commands reference 
 | 
   o     toggle sorting order (Total vs CurAvg/s) 
 | 
   p     filter by PID 
 | 
   q     quit 
 | 
   r     reset stats 
 | 
   s     set update interval 
 | 
   x     toggle reporting of stats for individual child trace events 
 | 
Press any other key to refresh statistics immediately. 
 | 
""" % (PATH_DEBUGFS_KVM, PATH_DEBUGFS_TRACING) 
 | 
  
 | 
    class PlainHelpFormatter(optparse.IndentedHelpFormatter): 
 | 
        def format_description(self, description): 
 | 
            if description: 
 | 
                return description + "\n" 
 | 
            else: 
 | 
                return "" 
 | 
  
 | 
    def cb_guest_to_pid(option, opt, val, parser): 
 | 
        try: 
 | 
            pids = Tui.get_pid_from_gname(val) 
 | 
        except: 
 | 
            sys.exit('Error while searching for guest "{}". Use "-p" to ' 
 | 
                     'specify a pid instead?'.format(val)) 
 | 
        if len(pids) == 0: 
 | 
            sys.exit('Error: No guest by the name "{}" found'.format(val)) 
 | 
        if len(pids) > 1: 
 | 
            sys.exit('Error: Multiple processes found (pids: {}). Use "-p" ' 
 | 
                     'to specify the desired pid'.format(" ".join(pids))) 
 | 
        parser.values.pid = pids[0] 
 | 
  
 | 
    optparser = optparse.OptionParser(description=description_text, 
 | 
                                      formatter=PlainHelpFormatter()) 
 | 
    optparser.add_option('-1', '--once', '--batch', 
 | 
                         action='store_true', 
 | 
                         default=False, 
 | 
                         dest='once', 
 | 
                         help='run in batch mode for one second', 
 | 
                         ) 
 | 
    optparser.add_option('-i', '--debugfs-include-past', 
 | 
                         action='store_true', 
 | 
                         default=False, 
 | 
                         dest='dbgfs_include_past', 
 | 
                         help='include all available data on past events for ' 
 | 
                              'debugfs', 
 | 
                         ) 
 | 
    optparser.add_option('-l', '--log', 
 | 
                         action='store_true', 
 | 
                         default=False, 
 | 
                         dest='log', 
 | 
                         help='run in logging mode (like vmstat)', 
 | 
                         ) 
 | 
    optparser.add_option('-t', '--tracepoints', 
 | 
                         action='store_true', 
 | 
                         default=False, 
 | 
                         dest='tracepoints', 
 | 
                         help='retrieve statistics from tracepoints', 
 | 
                         ) 
 | 
    optparser.add_option('-d', '--debugfs', 
 | 
                         action='store_true', 
 | 
                         default=False, 
 | 
                         dest='debugfs', 
 | 
                         help='retrieve statistics from debugfs', 
 | 
                         ) 
 | 
    optparser.add_option('-f', '--fields', 
 | 
                         action='store', 
 | 
                         default='', 
 | 
                         dest='fields', 
 | 
                         help='''fields to display (regex) 
 | 
                                 "-f help" for a list of available events''', 
 | 
                         ) 
 | 
    optparser.add_option('-p', '--pid', 
 | 
                         action='store', 
 | 
                         default=0, 
 | 
                         type='int', 
 | 
                         dest='pid', 
 | 
                         help='restrict statistics to pid', 
 | 
                         ) 
 | 
    optparser.add_option('-g', '--guest', 
 | 
                         action='callback', 
 | 
                         type='string', 
 | 
                         dest='pid', 
 | 
                         metavar='GUEST', 
 | 
                         help='restrict statistics to guest by name', 
 | 
                         callback=cb_guest_to_pid, 
 | 
                         ) 
 | 
    options, unkn = optparser.parse_args(sys.argv) 
 | 
    if len(unkn) != 1: 
 | 
        sys.exit('Error: Extra argument(s): ' + ' '.join(unkn[1:])) 
 | 
    try: 
 | 
        # verify that we were passed a valid regex up front 
 | 
        re.compile(options.fields) 
 | 
    except re.error: 
 | 
        sys.exit('Error: "' + options.fields + '" is not a valid regular ' 
 | 
                 'expression') 
 | 
  
 | 
    return options 
 | 
  
 | 
  
 | 
def check_access(options): 
 | 
    """Exits if the current user can't access all needed directories.""" 
 | 
    if not os.path.exists(PATH_DEBUGFS_TRACING) and (options.tracepoints or 
 | 
                                                     not options.debugfs): 
 | 
        sys.stderr.write("Please enable CONFIG_TRACING in your kernel " 
 | 
                         "when using the option -t (default).\n" 
 | 
                         "If it is enabled, make {0} readable by the " 
 | 
                         "current user.\n" 
 | 
                         .format(PATH_DEBUGFS_TRACING)) 
 | 
        if options.tracepoints: 
 | 
            sys.exit(1) 
 | 
  
 | 
        sys.stderr.write("Falling back to debugfs statistics!\n") 
 | 
        options.debugfs = True 
 | 
        time.sleep(5) 
 | 
  
 | 
    return options 
 | 
  
 | 
  
 | 
def assign_globals(): 
 | 
    global PATH_DEBUGFS_KVM 
 | 
    global PATH_DEBUGFS_TRACING 
 | 
  
 | 
    debugfs = '' 
 | 
    for line in open('/proc/mounts'): 
 | 
        if line.split(' ')[0] == 'debugfs': 
 | 
            debugfs = line.split(' ')[1] 
 | 
            break 
 | 
    if debugfs == '': 
 | 
        sys.stderr.write("Please make sure that CONFIG_DEBUG_FS is enabled in " 
 | 
                         "your kernel, mounted and\nreadable by the current " 
 | 
                         "user:\n" 
 | 
                         "('mount -t debugfs debugfs /sys/kernel/debug')\n") 
 | 
        sys.exit(1) 
 | 
  
 | 
    PATH_DEBUGFS_KVM = os.path.join(debugfs, 'kvm') 
 | 
    PATH_DEBUGFS_TRACING = os.path.join(debugfs, 'tracing') 
 | 
  
 | 
    if not os.path.exists(PATH_DEBUGFS_KVM): 
 | 
        sys.stderr.write("Please make sure that CONFIG_KVM is enabled in " 
 | 
                         "your kernel and that the modules are loaded.\n") 
 | 
        sys.exit(1) 
 | 
  
 | 
  
 | 
def main(): 
 | 
    assign_globals() 
 | 
    options = get_options() 
 | 
    options = check_access(options) 
 | 
  
 | 
    if (options.pid > 0 and 
 | 
        not os.path.isdir(os.path.join('/proc/', 
 | 
                                       str(options.pid)))): 
 | 
        sys.stderr.write('Did you use a (unsupported) tid instead of a pid?\n') 
 | 
        sys.exit('Specified pid does not exist.') 
 | 
  
 | 
    stats = Stats(options) 
 | 
  
 | 
    if options.fields == 'help': 
 | 
        stats.fields_filter = None 
 | 
        event_list = [] 
 | 
        for key in stats.get().keys(): 
 | 
            event_list.append(key.split('(', 1)[0]) 
 | 
        sys.stdout.write('  ' + '\n  '.join(sorted(set(event_list))) + '\n') 
 | 
        sys.exit(0) 
 | 
  
 | 
    if options.log: 
 | 
        log(stats) 
 | 
    elif not options.once: 
 | 
        with Tui(stats) as tui: 
 | 
            tui.show_stats() 
 | 
    else: 
 | 
        batch(stats) 
 | 
  
 | 
if __name__ == "__main__": 
 | 
    main() 
 |