cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

kvm_stat (62256B)


      1#!/usr/bin/env python3
      2# SPDX-License-Identifier: GPL-2.0-only
      3#
      4# top-like utility for displaying kvm statistics
      5#
      6# Copyright 2006-2008 Qumranet Technologies
      7# Copyright 2008-2011 Red Hat, Inc.
      8#
      9# Authors:
     10#  Avi Kivity <avi@redhat.com>
     11#
     12"""The kvm_stat module outputs statistics about running KVM VMs
     13
     14Three different ways of output formatting are available:
     15- as a top-like text ui
     16- in a key -> value format
     17- in an all keys, all values format
     18
     19The data is sampled from the KVM's debugfs entries and its perf events.
     20"""
     21from __future__ import print_function
     22
     23import curses
     24import sys
     25import locale
     26import os
     27import time
     28import argparse
     29import ctypes
     30import fcntl
     31import resource
     32import struct
     33import re
     34import subprocess
     35import signal
     36from collections import defaultdict, namedtuple
     37from functools import reduce
     38from datetime import datetime
     39
     40VMX_EXIT_REASONS = {
     41    'EXCEPTION_NMI':        0,
     42    'EXTERNAL_INTERRUPT':   1,
     43    'TRIPLE_FAULT':         2,
     44    'PENDING_INTERRUPT':    7,
     45    'NMI_WINDOW':           8,
     46    'TASK_SWITCH':          9,
     47    'CPUID':                10,
     48    'HLT':                  12,
     49    'INVLPG':               14,
     50    'RDPMC':                15,
     51    'RDTSC':                16,
     52    'VMCALL':               18,
     53    'VMCLEAR':              19,
     54    'VMLAUNCH':             20,
     55    'VMPTRLD':              21,
     56    'VMPTRST':              22,
     57    'VMREAD':               23,
     58    'VMRESUME':             24,
     59    'VMWRITE':              25,
     60    'VMOFF':                26,
     61    'VMON':                 27,
     62    'CR_ACCESS':            28,
     63    'DR_ACCESS':            29,
     64    'IO_INSTRUCTION':       30,
     65    'MSR_READ':             31,
     66    'MSR_WRITE':            32,
     67    'INVALID_STATE':        33,
     68    'MWAIT_INSTRUCTION':    36,
     69    'MONITOR_INSTRUCTION':  39,
     70    'PAUSE_INSTRUCTION':    40,
     71    'MCE_DURING_VMENTRY':   41,
     72    'TPR_BELOW_THRESHOLD':  43,
     73    'APIC_ACCESS':          44,
     74    'EPT_VIOLATION':        48,
     75    'EPT_MISCONFIG':        49,
     76    'WBINVD':               54,
     77    'XSETBV':               55,
     78    'APIC_WRITE':           56,
     79    'INVPCID':              58,
     80}
     81
     82SVM_EXIT_REASONS = {
     83    'READ_CR0':       0x000,
     84    'READ_CR3':       0x003,
     85    'READ_CR4':       0x004,
     86    'READ_CR8':       0x008,
     87    'WRITE_CR0':      0x010,
     88    'WRITE_CR3':      0x013,
     89    'WRITE_CR4':      0x014,
     90    'WRITE_CR8':      0x018,
     91    'READ_DR0':       0x020,
     92    'READ_DR1':       0x021,
     93    'READ_DR2':       0x022,
     94    'READ_DR3':       0x023,
     95    'READ_DR4':       0x024,
     96    'READ_DR5':       0x025,
     97    'READ_DR6':       0x026,
     98    'READ_DR7':       0x027,
     99    'WRITE_DR0':      0x030,
    100    'WRITE_DR1':      0x031,
    101    'WRITE_DR2':      0x032,
    102    'WRITE_DR3':      0x033,
    103    'WRITE_DR4':      0x034,
    104    'WRITE_DR5':      0x035,
    105    'WRITE_DR6':      0x036,
    106    'WRITE_DR7':      0x037,
    107    'EXCP_BASE':      0x040,
    108    'INTR':           0x060,
    109    'NMI':            0x061,
    110    'SMI':            0x062,
    111    'INIT':           0x063,
    112    'VINTR':          0x064,
    113    'CR0_SEL_WRITE':  0x065,
    114    'IDTR_READ':      0x066,
    115    'GDTR_READ':      0x067,
    116    'LDTR_READ':      0x068,
    117    'TR_READ':        0x069,
    118    'IDTR_WRITE':     0x06a,
    119    'GDTR_WRITE':     0x06b,
    120    'LDTR_WRITE':     0x06c,
    121    'TR_WRITE':       0x06d,
    122    'RDTSC':          0x06e,
    123    'RDPMC':          0x06f,
    124    'PUSHF':          0x070,
    125    'POPF':           0x071,
    126    'CPUID':          0x072,
    127    'RSM':            0x073,
    128    'IRET':           0x074,
    129    'SWINT':          0x075,
    130    'INVD':           0x076,
    131    'PAUSE':          0x077,
    132    'HLT':            0x078,
    133    'INVLPG':         0x079,
    134    'INVLPGA':        0x07a,
    135    'IOIO':           0x07b,
    136    'MSR':            0x07c,
    137    'TASK_SWITCH':    0x07d,
    138    'FERR_FREEZE':    0x07e,
    139    'SHUTDOWN':       0x07f,
    140    'VMRUN':          0x080,
    141    'VMMCALL':        0x081,
    142    'VMLOAD':         0x082,
    143    'VMSAVE':         0x083,
    144    'STGI':           0x084,
    145    'CLGI':           0x085,
    146    'SKINIT':         0x086,
    147    'RDTSCP':         0x087,
    148    'ICEBP':          0x088,
    149    'WBINVD':         0x089,
    150    'MONITOR':        0x08a,
    151    'MWAIT':          0x08b,
    152    'MWAIT_COND':     0x08c,
    153    'XSETBV':         0x08d,
    154    'NPF':            0x400,
    155}
    156
    157# EC definition of HSR (from arch/arm64/include/asm/kvm_arm.h)
    158AARCH64_EXIT_REASONS = {
    159    'UNKNOWN':      0x00,
    160    'WFI':          0x01,
    161    'CP15_32':      0x03,
    162    'CP15_64':      0x04,
    163    'CP14_MR':      0x05,
    164    'CP14_LS':      0x06,
    165    'FP_ASIMD':     0x07,
    166    'CP10_ID':      0x08,
    167    'CP14_64':      0x0C,
    168    'ILL_ISS':      0x0E,
    169    'SVC32':        0x11,
    170    'HVC32':        0x12,
    171    'SMC32':        0x13,
    172    'SVC64':        0x15,
    173    'HVC64':        0x16,
    174    'SMC64':        0x17,
    175    'SYS64':        0x18,
    176    'IABT':         0x20,
    177    'IABT_HYP':     0x21,
    178    'PC_ALIGN':     0x22,
    179    'DABT':         0x24,
    180    'DABT_HYP':     0x25,
    181    'SP_ALIGN':     0x26,
    182    'FP_EXC32':     0x28,
    183    'FP_EXC64':     0x2C,
    184    'SERROR':       0x2F,
    185    'BREAKPT':      0x30,
    186    'BREAKPT_HYP':  0x31,
    187    'SOFTSTP':      0x32,
    188    'SOFTSTP_HYP':  0x33,
    189    'WATCHPT':      0x34,
    190    'WATCHPT_HYP':  0x35,
    191    'BKPT32':       0x38,
    192    'VECTOR32':     0x3A,
    193    'BRK64':        0x3C,
    194}
    195
    196# From include/uapi/linux/kvm.h, KVM_EXIT_xxx
    197USERSPACE_EXIT_REASONS = {
    198    'UNKNOWN':          0,
    199    'EXCEPTION':        1,
    200    'IO':               2,
    201    'HYPERCALL':        3,
    202    'DEBUG':            4,
    203    'HLT':              5,
    204    'MMIO':             6,
    205    'IRQ_WINDOW_OPEN':  7,
    206    'SHUTDOWN':         8,
    207    'FAIL_ENTRY':       9,
    208    'INTR':             10,
    209    'SET_TPR':          11,
    210    'TPR_ACCESS':       12,
    211    'S390_SIEIC':       13,
    212    'S390_RESET':       14,
    213    'DCR':              15,
    214    'NMI':              16,
    215    'INTERNAL_ERROR':   17,
    216    'OSI':              18,
    217    'PAPR_HCALL':       19,
    218    'S390_UCONTROL':    20,
    219    'WATCHDOG':         21,
    220    'S390_TSCH':        22,
    221    'EPR':              23,
    222    'SYSTEM_EVENT':     24,
    223}
    224
    225IOCTL_NUMBERS = {
    226    'SET_FILTER':  0x40082406,
    227    'ENABLE':      0x00002400,
    228    'DISABLE':     0x00002401,
    229    'RESET':       0x00002403,
    230}
    231
    232signal_received = False
    233
    234ENCODING = locale.getpreferredencoding(False)
    235TRACE_FILTER = re.compile(r'^[^\(]*$')
    236
    237
    238class Arch(object):
    239    """Encapsulates global architecture specific data.
    240
    241    Contains the performance event open syscall and ioctl numbers, as
    242    well as the VM exit reasons for the architecture it runs on.
    243
    244    """
    245    @staticmethod
    246    def get_arch():
    247        machine = os.uname()[4]
    248
    249        if machine.startswith('ppc'):
    250            return ArchPPC()
    251        elif machine.startswith('aarch64'):
    252            return ArchA64()
    253        elif machine.startswith('s390'):
    254            return ArchS390()
    255        else:
    256            # X86_64
    257            for line in open('/proc/cpuinfo'):
    258                if not line.startswith('flags'):
    259                    continue
    260
    261                flags = line.split()
    262                if 'vmx' in flags:
    263                    return ArchX86(VMX_EXIT_REASONS)
    264                if 'svm' in flags:
    265                    return ArchX86(SVM_EXIT_REASONS)
    266                return
    267
    268    def tracepoint_is_child(self, field):
    269        if (TRACE_FILTER.match(field)):
    270            return None
    271        return field.split('(', 1)[0]
    272
    273
    274class ArchX86(Arch):
    275    def __init__(self, exit_reasons):
    276        self.sc_perf_evt_open = 298
    277        self.ioctl_numbers = IOCTL_NUMBERS
    278        self.exit_reason_field = 'exit_reason'
    279        self.exit_reasons = exit_reasons
    280
    281    def debugfs_is_child(self, field):
    282        """ Returns name of parent if 'field' is a child, None otherwise """
    283        return None
    284
    285
    286class ArchPPC(Arch):
    287    def __init__(self):
    288        self.sc_perf_evt_open = 319
    289        self.ioctl_numbers = IOCTL_NUMBERS
    290        self.ioctl_numbers['ENABLE'] = 0x20002400
    291        self.ioctl_numbers['DISABLE'] = 0x20002401
    292        self.ioctl_numbers['RESET'] = 0x20002403
    293
    294        # PPC comes in 32 and 64 bit and some generated ioctl
    295        # numbers depend on the wordsize.
    296        char_ptr_size = ctypes.sizeof(ctypes.c_char_p)
    297        self.ioctl_numbers['SET_FILTER'] = 0x80002406 | char_ptr_size << 16
    298        self.exit_reason_field = 'exit_nr'
    299        self.exit_reasons = {}
    300
    301    def debugfs_is_child(self, field):
    302        """ Returns name of parent if 'field' is a child, None otherwise """
    303        return None
    304
    305
    306class ArchA64(Arch):
    307    def __init__(self):
    308        self.sc_perf_evt_open = 241
    309        self.ioctl_numbers = IOCTL_NUMBERS
    310        self.exit_reason_field = 'esr_ec'
    311        self.exit_reasons = AARCH64_EXIT_REASONS
    312
    313    def debugfs_is_child(self, field):
    314        """ Returns name of parent if 'field' is a child, None otherwise """
    315        return None
    316
    317
    318class ArchS390(Arch):
    319    def __init__(self):
    320        self.sc_perf_evt_open = 331
    321        self.ioctl_numbers = IOCTL_NUMBERS
    322        self.exit_reason_field = None
    323        self.exit_reasons = None
    324
    325    def debugfs_is_child(self, field):
    326        """ Returns name of parent if 'field' is a child, None otherwise """
    327        if field.startswith('instruction_'):
    328            return 'exit_instruction'
    329
    330
    331ARCH = Arch.get_arch()
    332
    333
    334class perf_event_attr(ctypes.Structure):
    335    """Struct that holds the necessary data to set up a trace event.
    336
    337    For an extensive explanation see perf_event_open(2) and
    338    include/uapi/linux/perf_event.h, struct perf_event_attr
    339
    340    All fields that are not initialized in the constructor are 0.
    341
    342    """
    343    _fields_ = [('type', ctypes.c_uint32),
    344                ('size', ctypes.c_uint32),
    345                ('config', ctypes.c_uint64),
    346                ('sample_freq', ctypes.c_uint64),
    347                ('sample_type', ctypes.c_uint64),
    348                ('read_format', ctypes.c_uint64),
    349                ('flags', ctypes.c_uint64),
    350                ('wakeup_events', ctypes.c_uint32),
    351                ('bp_type', ctypes.c_uint32),
    352                ('bp_addr', ctypes.c_uint64),
    353                ('bp_len', ctypes.c_uint64),
    354                ]
    355
    356    def __init__(self):
    357        super(self.__class__, self).__init__()
    358        self.type = PERF_TYPE_TRACEPOINT
    359        self.size = ctypes.sizeof(self)
    360        self.read_format = PERF_FORMAT_GROUP
    361
    362
    363PERF_TYPE_TRACEPOINT = 2
    364PERF_FORMAT_GROUP = 1 << 3
    365
    366
    367class Group(object):
    368    """Represents a perf event group."""
    369
    370    def __init__(self):
    371        self.events = []
    372
    373    def add_event(self, event):
    374        self.events.append(event)
    375
    376    def read(self):
    377        """Returns a dict with 'event name: value' for all events in the
    378        group.
    379
    380        Values are read by reading from the file descriptor of the
    381        event that is the group leader. See perf_event_open(2) for
    382        details.
    383
    384        Read format for the used event configuration is:
    385        struct read_format {
    386            u64 nr; /* The number of events */
    387            struct {
    388                u64 value; /* The value of the event */
    389            } values[nr];
    390        };
    391
    392        """
    393        length = 8 * (1 + len(self.events))
    394        read_format = 'xxxxxxxx' + 'Q' * len(self.events)
    395        return dict(zip([event.name for event in self.events],
    396                        struct.unpack(read_format,
    397                                      os.read(self.events[0].fd, length))))
    398
    399
    400class Event(object):
    401    """Represents a performance event and manages its life cycle."""
    402    def __init__(self, name, group, trace_cpu, trace_pid, trace_point,
    403                 trace_filter, trace_set='kvm'):
    404        self.libc = ctypes.CDLL('libc.so.6', use_errno=True)
    405        self.syscall = self.libc.syscall
    406        self.name = name
    407        self.fd = None
    408        self._setup_event(group, trace_cpu, trace_pid, trace_point,
    409                          trace_filter, trace_set)
    410
    411    def __del__(self):
    412        """Closes the event's file descriptor.
    413
    414        As no python file object was created for the file descriptor,
    415        python will not reference count the descriptor and will not
    416        close it itself automatically, so we do it.
    417
    418        """
    419        if self.fd:
    420            os.close(self.fd)
    421
    422    def _perf_event_open(self, attr, pid, cpu, group_fd, flags):
    423        """Wrapper for the sys_perf_evt_open() syscall.
    424
    425        Used to set up performance events, returns a file descriptor or -1
    426        on error.
    427
    428        Attributes are:
    429        - syscall number
    430        - struct perf_event_attr *
    431        - pid or -1 to monitor all pids
    432        - cpu number or -1 to monitor all cpus
    433        - The file descriptor of the group leader or -1 to create a group.
    434        - flags
    435
    436        """
    437        return self.syscall(ARCH.sc_perf_evt_open, ctypes.pointer(attr),
    438                            ctypes.c_int(pid), ctypes.c_int(cpu),
    439                            ctypes.c_int(group_fd), ctypes.c_long(flags))
    440
    441    def _setup_event_attribute(self, trace_set, trace_point):
    442        """Returns an initialized ctype perf_event_attr struct."""
    443
    444        id_path = os.path.join(PATH_DEBUGFS_TRACING, 'events', trace_set,
    445                               trace_point, 'id')
    446
    447        event_attr = perf_event_attr()
    448        event_attr.config = int(open(id_path).read())
    449        return event_attr
    450
    451    def _setup_event(self, group, trace_cpu, trace_pid, trace_point,
    452                     trace_filter, trace_set):
    453        """Sets up the perf event in Linux.
    454
    455        Issues the syscall to register the event in the kernel and
    456        then sets the optional filter.
    457
    458        """
    459
    460        event_attr = self._setup_event_attribute(trace_set, trace_point)
    461
    462        # First event will be group leader.
    463        group_leader = -1
    464
    465        # All others have to pass the leader's descriptor instead.
    466        if group.events:
    467            group_leader = group.events[0].fd
    468
    469        fd = self._perf_event_open(event_attr, trace_pid,
    470                                   trace_cpu, group_leader, 0)
    471        if fd == -1:
    472            err = ctypes.get_errno()
    473            raise OSError(err, os.strerror(err),
    474                          'while calling sys_perf_event_open().')
    475
    476        if trace_filter:
    477            fcntl.ioctl(fd, ARCH.ioctl_numbers['SET_FILTER'],
    478                        trace_filter)
    479
    480        self.fd = fd
    481
    482    def enable(self):
    483        """Enables the trace event in the kernel.
    484
    485        Enabling the group leader makes reading counters from it and the
    486        events under it possible.
    487
    488        """
    489        fcntl.ioctl(self.fd, ARCH.ioctl_numbers['ENABLE'], 0)
    490
    491    def disable(self):
    492        """Disables the trace event in the kernel.
    493
    494        Disabling the group leader makes reading all counters under it
    495        impossible.
    496
    497        """
    498        fcntl.ioctl(self.fd, ARCH.ioctl_numbers['DISABLE'], 0)
    499
    500    def reset(self):
    501        """Resets the count of the trace event in the kernel."""
    502        fcntl.ioctl(self.fd, ARCH.ioctl_numbers['RESET'], 0)
    503
    504
    505class Provider(object):
    506    """Encapsulates functionalities used by all providers."""
    507    def __init__(self, pid):
    508        self.child_events = False
    509        self.pid = pid
    510
    511    @staticmethod
    512    def is_field_wanted(fields_filter, field):
    513        """Indicate whether field is valid according to fields_filter."""
    514        if not fields_filter:
    515            return True
    516        return re.match(fields_filter, field) is not None
    517
    518    @staticmethod
    519    def walkdir(path):
    520        """Returns os.walk() data for specified directory.
    521
    522        As it is only a wrapper it returns the same 3-tuple of (dirpath,
    523        dirnames, filenames).
    524        """
    525        return next(os.walk(path))
    526
    527
    528class TracepointProvider(Provider):
    529    """Data provider for the stats class.
    530
    531    Manages the events/groups from which it acquires its data.
    532
    533    """
    534    def __init__(self, pid, fields_filter):
    535        self.group_leaders = []
    536        self.filters = self._get_filters()
    537        self.update_fields(fields_filter)
    538        super(TracepointProvider, self).__init__(pid)
    539
    540    @staticmethod
    541    def _get_filters():
    542        """Returns a dict of trace events, their filter ids and
    543        the values that can be filtered.
    544
    545        Trace events can be filtered for special values by setting a
    546        filter string via an ioctl. The string normally has the format
    547        identifier==value. For each filter a new event will be created, to
    548        be able to distinguish the events.
    549
    550        """
    551        filters = {}
    552        filters['kvm_userspace_exit'] = ('reason', USERSPACE_EXIT_REASONS)
    553        if ARCH.exit_reason_field and ARCH.exit_reasons:
    554            filters['kvm_exit'] = (ARCH.exit_reason_field, ARCH.exit_reasons)
    555        return filters
    556
    557    def _get_available_fields(self):
    558        """Returns a list of available events of format 'event name(filter
    559        name)'.
    560
    561        All available events have directories under
    562        /sys/kernel/debug/tracing/events/ which export information
    563        about the specific event. Therefore, listing the dirs gives us
    564        a list of all available events.
    565
    566        Some events like the vm exit reasons can be filtered for
    567        specific values. To take account for that, the routine below
    568        creates special fields with the following format:
    569        event name(filter name)
    570
    571        """
    572        path = os.path.join(PATH_DEBUGFS_TRACING, 'events', 'kvm')
    573        fields = self.walkdir(path)[1]
    574        extra = []
    575        for field in fields:
    576            if field in self.filters:
    577                filter_name_, filter_dicts = self.filters[field]
    578                for name in filter_dicts:
    579                    extra.append(field + '(' + name + ')')
    580        fields += extra
    581        return fields
    582
    583    def update_fields(self, fields_filter):
    584        """Refresh fields, applying fields_filter"""
    585        self.fields = [field for field in self._get_available_fields()
    586                       if self.is_field_wanted(fields_filter, field)]
    587        # add parents for child fields - otherwise we won't see any output!
    588        for field in self._fields:
    589            parent = ARCH.tracepoint_is_child(field)
    590            if (parent and parent not in self._fields):
    591                self.fields.append(parent)
    592
    593    @staticmethod
    594    def _get_online_cpus():
    595        """Returns a list of cpu id integers."""
    596        def parse_int_list(list_string):
    597            """Returns an int list from a string of comma separated integers and
    598            integer ranges."""
    599            integers = []
    600            members = list_string.split(',')
    601
    602            for member in members:
    603                if '-' not in member:
    604                    integers.append(int(member))
    605                else:
    606                    int_range = member.split('-')
    607                    integers.extend(range(int(int_range[0]),
    608                                          int(int_range[1]) + 1))
    609
    610            return integers
    611
    612        with open('/sys/devices/system/cpu/online') as cpu_list:
    613            cpu_string = cpu_list.readline()
    614            return parse_int_list(cpu_string)
    615
    616    def _setup_traces(self):
    617        """Creates all event and group objects needed to be able to retrieve
    618        data."""
    619        fields = self._get_available_fields()
    620        if self._pid > 0:
    621            # Fetch list of all threads of the monitored pid, as qemu
    622            # starts a thread for each vcpu.
    623            path = os.path.join('/proc', str(self._pid), 'task')
    624            groupids = self.walkdir(path)[1]
    625        else:
    626            groupids = self._get_online_cpus()
    627
    628        # The constant is needed as a buffer for python libs, std
    629        # streams and other files that the script opens.
    630        newlim = len(groupids) * len(fields) + 50
    631        try:
    632            softlim_, hardlim = resource.getrlimit(resource.RLIMIT_NOFILE)
    633
    634            if hardlim < newlim:
    635                # Now we need CAP_SYS_RESOURCE, to increase the hard limit.
    636                resource.setrlimit(resource.RLIMIT_NOFILE, (newlim, newlim))
    637            else:
    638                # Raising the soft limit is sufficient.
    639                resource.setrlimit(resource.RLIMIT_NOFILE, (newlim, hardlim))
    640
    641        except ValueError:
    642            sys.exit("NOFILE rlimit could not be raised to {0}".format(newlim))
    643
    644        for groupid in groupids:
    645            group = Group()
    646            for name in fields:
    647                tracepoint = name
    648                tracefilter = None
    649                match = re.match(r'(.*)\((.*)\)', name)
    650                if match:
    651                    tracepoint, sub = match.groups()
    652                    tracefilter = ('%s==%d\0' %
    653                                   (self.filters[tracepoint][0],
    654                                    self.filters[tracepoint][1][sub]))
    655
    656                # From perf_event_open(2):
    657                # pid > 0 and cpu == -1
    658                # This measures the specified process/thread on any CPU.
    659                #
    660                # pid == -1 and cpu >= 0
    661                # This measures all processes/threads on the specified CPU.
    662                trace_cpu = groupid if self._pid == 0 else -1
    663                trace_pid = int(groupid) if self._pid != 0 else -1
    664
    665                group.add_event(Event(name=name,
    666                                      group=group,
    667                                      trace_cpu=trace_cpu,
    668                                      trace_pid=trace_pid,
    669                                      trace_point=tracepoint,
    670                                      trace_filter=tracefilter))
    671
    672            self.group_leaders.append(group)
    673
    674    @property
    675    def fields(self):
    676        return self._fields
    677
    678    @fields.setter
    679    def fields(self, fields):
    680        """Enables/disables the (un)wanted events"""
    681        self._fields = fields
    682        for group in self.group_leaders:
    683            for index, event in enumerate(group.events):
    684                if event.name in fields:
    685                    event.reset()
    686                    event.enable()
    687                else:
    688                    # Do not disable the group leader.
    689                    # It would disable all of its events.
    690                    if index != 0:
    691                        event.disable()
    692
    693    @property
    694    def pid(self):
    695        return self._pid
    696
    697    @pid.setter
    698    def pid(self, pid):
    699        """Changes the monitored pid by setting new traces."""
    700        self._pid = pid
    701        # The garbage collector will get rid of all Event/Group
    702        # objects and open files after removing the references.
    703        self.group_leaders = []
    704        self._setup_traces()
    705        self.fields = self._fields
    706
    707    def read(self, by_guest=0):
    708        """Returns 'event name: current value' for all enabled events."""
    709        ret = defaultdict(int)
    710        for group in self.group_leaders:
    711            for name, val in group.read().items():
    712                if name not in self._fields:
    713                    continue
    714                parent = ARCH.tracepoint_is_child(name)
    715                if parent:
    716                    name += ' ' + parent
    717                ret[name] += val
    718        return ret
    719
    720    def reset(self):
    721        """Reset all field counters"""
    722        for group in self.group_leaders:
    723            for event in group.events:
    724                event.reset()
    725
    726
    727class DebugfsProvider(Provider):
    728    """Provides data from the files that KVM creates in the kvm debugfs
    729    folder."""
    730    def __init__(self, pid, fields_filter, include_past):
    731        self.update_fields(fields_filter)
    732        self._baseline = {}
    733        self.do_read = True
    734        self.paths = []
    735        super(DebugfsProvider, self).__init__(pid)
    736        if include_past:
    737            self._restore()
    738
    739    def _get_available_fields(self):
    740        """"Returns a list of available fields.
    741
    742        The fields are all available KVM debugfs files
    743
    744        """
    745        exempt_list = ['halt_poll_fail_ns', 'halt_poll_success_ns', 'halt_wait_ns']
    746        fields = [field for field in self.walkdir(PATH_DEBUGFS_KVM)[2]
    747                  if field not in exempt_list]
    748
    749        return fields
    750
    751    def update_fields(self, fields_filter):
    752        """Refresh fields, applying fields_filter"""
    753        self._fields = [field for field in self._get_available_fields()
    754                        if self.is_field_wanted(fields_filter, field)]
    755        # add parents for child fields - otherwise we won't see any output!
    756        for field in self._fields:
    757            parent = ARCH.debugfs_is_child(field)
    758            if (parent and parent not in self._fields):
    759                self.fields.append(parent)
    760
    761    @property
    762    def fields(self):
    763        return self._fields
    764
    765    @fields.setter
    766    def fields(self, fields):
    767        self._fields = fields
    768        self.reset()
    769
    770    @property
    771    def pid(self):
    772        return self._pid
    773
    774    @pid.setter
    775    def pid(self, pid):
    776        self._pid = pid
    777        if pid != 0:
    778            vms = self.walkdir(PATH_DEBUGFS_KVM)[1]
    779            if len(vms) == 0:
    780                self.do_read = False
    781
    782            self.paths = list(filter(lambda x: "{}-".format(pid) in x, vms))
    783
    784        else:
    785            self.paths = []
    786            self.do_read = True
    787
    788    def _verify_paths(self):
    789        """Remove invalid paths"""
    790        for path in self.paths:
    791            if not os.path.exists(os.path.join(PATH_DEBUGFS_KVM, path)):
    792                self.paths.remove(path)
    793                continue
    794
    795    def read(self, reset=0, by_guest=0):
    796        """Returns a dict with format:'file name / field -> current value'.
    797
    798        Parameter 'reset':
    799          0   plain read
    800          1   reset field counts to 0
    801          2   restore the original field counts
    802
    803        """
    804        results = {}
    805
    806        # If no debugfs filtering support is available, then don't read.
    807        if not self.do_read:
    808            return results
    809        self._verify_paths()
    810
    811        paths = self.paths
    812        if self._pid == 0:
    813            paths = []
    814            for entry in os.walk(PATH_DEBUGFS_KVM):
    815                for dir in entry[1]:
    816                    paths.append(dir)
    817        for path in paths:
    818            for field in self._fields:
    819                value = self._read_field(field, path)
    820                key = path + field
    821                if reset == 1:
    822                    self._baseline[key] = value
    823                if reset == 2:
    824                    self._baseline[key] = 0
    825                if self._baseline.get(key, -1) == -1:
    826                    self._baseline[key] = value
    827                parent = ARCH.debugfs_is_child(field)
    828                if parent:
    829                    field = field + ' ' + parent
    830                else:
    831                    if by_guest:
    832                        field = key.split('-')[0]    # set 'field' to 'pid'
    833                increment = value - self._baseline.get(key, 0)
    834                if field in results:
    835                    results[field] += increment
    836                else:
    837                    results[field] = increment
    838
    839        return results
    840
    841    def _read_field(self, field, path):
    842        """Returns the value of a single field from a specific VM."""
    843        try:
    844            return int(open(os.path.join(PATH_DEBUGFS_KVM,
    845                                         path,
    846                                         field))
    847                       .read())
    848        except IOError:
    849            return 0
    850
    851    def reset(self):
    852        """Reset field counters"""
    853        self._baseline = {}
    854        self.read(1)
    855
    856    def _restore(self):
    857        """Reset field counters"""
    858        self._baseline = {}
    859        self.read(2)
    860
    861
    862EventStat = namedtuple('EventStat', ['value', 'delta'])
    863
    864
    865class Stats(object):
    866    """Manages the data providers and the data they provide.
    867
    868    It is used to set filters on the provider's data and collect all
    869    provider data.
    870
    871    """
    872    def __init__(self, options):
    873        self.providers = self._get_providers(options)
    874        self._pid_filter = options.pid
    875        self._fields_filter = options.fields
    876        self.values = {}
    877        self._child_events = False
    878
    879    def _get_providers(self, options):
    880        """Returns a list of data providers depending on the passed options."""
    881        providers = []
    882
    883        if options.debugfs:
    884            providers.append(DebugfsProvider(options.pid, options.fields,
    885                                             options.debugfs_include_past))
    886        if options.tracepoints or not providers:
    887            providers.append(TracepointProvider(options.pid, options.fields))
    888
    889        return providers
    890
    891    def _update_provider_filters(self):
    892        """Propagates fields filters to providers."""
    893        # As we reset the counters when updating the fields we can
    894        # also clear the cache of old values.
    895        self.values = {}
    896        for provider in self.providers:
    897            provider.update_fields(self._fields_filter)
    898
    899    def reset(self):
    900        self.values = {}
    901        for provider in self.providers:
    902            provider.reset()
    903
    904    @property
    905    def fields_filter(self):
    906        return self._fields_filter
    907
    908    @fields_filter.setter
    909    def fields_filter(self, fields_filter):
    910        if fields_filter != self._fields_filter:
    911            self._fields_filter = fields_filter
    912            self._update_provider_filters()
    913
    914    @property
    915    def pid_filter(self):
    916        return self._pid_filter
    917
    918    @pid_filter.setter
    919    def pid_filter(self, pid):
    920        if pid != self._pid_filter:
    921            self._pid_filter = pid
    922            self.values = {}
    923            for provider in self.providers:
    924                provider.pid = self._pid_filter
    925
    926    @property
    927    def child_events(self):
    928        return self._child_events
    929
    930    @child_events.setter
    931    def child_events(self, val):
    932        self._child_events = val
    933        for provider in self.providers:
    934            provider.child_events = val
    935
    936    def get(self, by_guest=0):
    937        """Returns a dict with field -> (value, delta to last value) of all
    938        provider data.
    939        Key formats:
    940          * plain: 'key' is event name
    941          * child-parent: 'key' is in format '<child> <parent>'
    942          * pid: 'key' is the pid of the guest, and the record contains the
    943               aggregated event data
    944        These formats are generated by the providers, and handled in class TUI.
    945        """
    946        for provider in self.providers:
    947            new = provider.read(by_guest=by_guest)
    948            for key in new:
    949                oldval = self.values.get(key, EventStat(0, 0)).value
    950                newval = new.get(key, 0)
    951                newdelta = newval - oldval
    952                self.values[key] = EventStat(newval, newdelta)
    953        return self.values
    954
    955    def toggle_display_guests(self, to_pid):
    956        """Toggle between collection of stats by individual event and by
    957        guest pid
    958
    959        Events reported by DebugfsProvider change when switching to/from
    960        reading by guest values. Hence we have to remove the excess event
    961        names from self.values.
    962
    963        """
    964        if any(isinstance(ins, TracepointProvider) for ins in self.providers):
    965            return 1
    966        if to_pid:
    967            for provider in self.providers:
    968                if isinstance(provider, DebugfsProvider):
    969                    for key in provider.fields:
    970                        if key in self.values.keys():
    971                            del self.values[key]
    972        else:
    973            oldvals = self.values.copy()
    974            for key in oldvals:
    975                if key.isdigit():
    976                    del self.values[key]
    977        # Update oldval (see get())
    978        self.get(to_pid)
    979        return 0
    980
    981
    982DELAY_DEFAULT = 3.0
    983MAX_GUEST_NAME_LEN = 48
    984MAX_REGEX_LEN = 44
    985SORT_DEFAULT = 0
    986MIN_DELAY = 0.1
    987MAX_DELAY = 25.5
    988
    989
    990class Tui(object):
    991    """Instruments curses to draw a nice text ui."""
    992    def __init__(self, stats, opts):
    993        self.stats = stats
    994        self.screen = None
    995        self._delay_initial = 0.25
    996        self._delay_regular = opts.set_delay
    997        self._sorting = SORT_DEFAULT
    998        self._display_guests = 0
    999
   1000    def __enter__(self):
   1001        """Initialises curses for later use.  Based on curses.wrapper
   1002           implementation from the Python standard library."""
   1003        self.screen = curses.initscr()
   1004        curses.noecho()
   1005        curses.cbreak()
   1006
   1007        # The try/catch works around a minor bit of
   1008        # over-conscientiousness in the curses module, the error
   1009        # return from C start_color() is ignorable.
   1010        try:
   1011            curses.start_color()
   1012        except curses.error:
   1013            pass
   1014
   1015        # Hide cursor in extra statement as some monochrome terminals
   1016        # might support hiding but not colors.
   1017        try:
   1018            curses.curs_set(0)
   1019        except curses.error:
   1020            pass
   1021
   1022        curses.use_default_colors()
   1023        return self
   1024
   1025    def __exit__(self, *exception):
   1026        """Resets the terminal to its normal state.  Based on curses.wrapper
   1027           implementation from the Python standard library."""
   1028        if self.screen:
   1029            self.screen.keypad(0)
   1030            curses.echo()
   1031            curses.nocbreak()
   1032            curses.endwin()
   1033
   1034    @staticmethod
   1035    def get_all_gnames():
   1036        """Returns a list of (pid, gname) tuples of all running guests"""
   1037        res = []
   1038        try:
   1039            child = subprocess.Popen(['ps', '-A', '--format', 'pid,args'],
   1040                                     stdout=subprocess.PIPE)
   1041        except:
   1042            raise Exception
   1043        for line in child.stdout:
   1044            line = line.decode(ENCODING).lstrip().split(' ', 1)
   1045            # perform a sanity check before calling the more expensive
   1046            # function to possibly extract the guest name
   1047            if ' -name ' in line[1]:
   1048                res.append((line[0], Tui.get_gname_from_pid(line[0])))
   1049        child.stdout.close()
   1050
   1051        return res
   1052
   1053    def _print_all_gnames(self, row):
   1054        """Print a list of all running guests along with their pids."""
   1055        self.screen.addstr(row, 2, '%8s  %-60s' %
   1056                           ('Pid', 'Guest Name (fuzzy list, might be '
   1057                            'inaccurate!)'),
   1058                           curses.A_UNDERLINE)
   1059        row += 1
   1060        try:
   1061            for line in self.get_all_gnames():
   1062                self.screen.addstr(row, 2, '%8s  %-60s' % (line[0], line[1]))
   1063                row += 1
   1064                if row >= self.screen.getmaxyx()[0]:
   1065                    break
   1066        except Exception:
   1067            self.screen.addstr(row + 1, 2, 'Not available')
   1068
   1069    @staticmethod
   1070    def get_pid_from_gname(gname):
   1071        """Fuzzy function to convert guest name to QEMU process pid.
   1072
   1073        Returns a list of potential pids, can be empty if no match found.
   1074        Throws an exception on processing errors.
   1075
   1076        """
   1077        pids = []
   1078        for line in Tui.get_all_gnames():
   1079            if gname == line[1]:
   1080                pids.append(int(line[0]))
   1081
   1082        return pids
   1083
   1084    @staticmethod
   1085    def get_gname_from_pid(pid):
   1086        """Returns the guest name for a QEMU process pid.
   1087
   1088        Extracts the guest name from the QEMU comma line by processing the
   1089        '-name' option. Will also handle names specified out of sequence.
   1090
   1091        """
   1092        name = ''
   1093        try:
   1094            line = open('/proc/{}/cmdline'
   1095                        .format(pid), 'r').read().split('\0')
   1096            parms = line[line.index('-name') + 1].split(',')
   1097            while '' in parms:
   1098                # commas are escaped (i.e. ',,'), hence e.g. 'foo,bar' results
   1099                # in # ['foo', '', 'bar'], which we revert here
   1100                idx = parms.index('')
   1101                parms[idx - 1] += ',' + parms[idx + 1]
   1102                del parms[idx:idx+2]
   1103            # the '-name' switch allows for two ways to specify the guest name,
   1104            # where the plain name overrides the name specified via 'guest='
   1105            for arg in parms:
   1106                if '=' not in arg:
   1107                    name = arg
   1108                    break
   1109                if arg[:6] == 'guest=':
   1110                    name = arg[6:]
   1111        except (ValueError, IOError, IndexError):
   1112            pass
   1113
   1114        return name
   1115
   1116    def _update_pid(self, pid):
   1117        """Propagates pid selection to stats object."""
   1118        self.screen.addstr(4, 1, 'Updating pid filter...')
   1119        self.screen.refresh()
   1120        self.stats.pid_filter = pid
   1121
   1122    def _refresh_header(self, pid=None):
   1123        """Refreshes the header."""
   1124        if pid is None:
   1125            pid = self.stats.pid_filter
   1126        self.screen.erase()
   1127        gname = self.get_gname_from_pid(pid)
   1128        self._gname = gname
   1129        if gname:
   1130            gname = ('({})'.format(gname[:MAX_GUEST_NAME_LEN] + '...'
   1131                                   if len(gname) > MAX_GUEST_NAME_LEN
   1132                                   else gname))
   1133        if pid > 0:
   1134            self._headline = 'kvm statistics - pid {0} {1}'.format(pid, gname)
   1135        else:
   1136            self._headline = 'kvm statistics - summary'
   1137        self.screen.addstr(0, 0, self._headline, curses.A_BOLD)
   1138        if self.stats.fields_filter:
   1139            regex = self.stats.fields_filter
   1140            if len(regex) > MAX_REGEX_LEN:
   1141                regex = regex[:MAX_REGEX_LEN] + '...'
   1142            self.screen.addstr(1, 17, 'regex filter: {0}'.format(regex))
   1143        if self._display_guests:
   1144            col_name = 'Guest Name'
   1145        else:
   1146            col_name = 'Event'
   1147        self.screen.addstr(2, 1, '%-40s %10s%7s %8s' %
   1148                           (col_name, 'Total', '%Total', 'CurAvg/s'),
   1149                           curses.A_STANDOUT)
   1150        self.screen.addstr(4, 1, 'Collecting data...')
   1151        self.screen.refresh()
   1152
   1153    def _refresh_body(self, sleeptime):
   1154        def insert_child(sorted_items, child, values, parent):
   1155            num = len(sorted_items)
   1156            for i in range(0, num):
   1157                # only add child if parent is present
   1158                if parent.startswith(sorted_items[i][0]):
   1159                    sorted_items.insert(i + 1, ('  ' + child, values))
   1160
   1161        def get_sorted_events(self, stats):
   1162            """ separate parent and child events """
   1163            if self._sorting == SORT_DEFAULT:
   1164                def sortkey(pair):
   1165                    # sort by (delta value, overall value)
   1166                    v = pair[1]
   1167                    return (v.delta, v.value)
   1168            else:
   1169                def sortkey(pair):
   1170                    # sort by overall value
   1171                    v = pair[1]
   1172                    return v.value
   1173
   1174            childs = []
   1175            sorted_items = []
   1176            # we can't rule out child events to appear prior to parents even
   1177            # when sorted - separate out all children first, and add in later
   1178            for key, values in sorted(stats.items(), key=sortkey,
   1179                                      reverse=True):
   1180                if values == (0, 0):
   1181                    continue
   1182                if key.find(' ') != -1:
   1183                    if not self.stats.child_events:
   1184                        continue
   1185                    childs.insert(0, (key, values))
   1186                else:
   1187                    sorted_items.append((key, values))
   1188            if self.stats.child_events:
   1189                for key, values in childs:
   1190                    (child, parent) = key.split(' ')
   1191                    insert_child(sorted_items, child, values, parent)
   1192
   1193            return sorted_items
   1194
   1195        if not self._is_running_guest(self.stats.pid_filter):
   1196            if self._gname:
   1197                try:  # ...to identify the guest by name in case it's back
   1198                    pids = self.get_pid_from_gname(self._gname)
   1199                    if len(pids) == 1:
   1200                        self._refresh_header(pids[0])
   1201                        self._update_pid(pids[0])
   1202                        return
   1203                except:
   1204                    pass
   1205            self._display_guest_dead()
   1206            # leave final data on screen
   1207            return
   1208        row = 3
   1209        self.screen.move(row, 0)
   1210        self.screen.clrtobot()
   1211        stats = self.stats.get(self._display_guests)
   1212        total = 0.
   1213        ctotal = 0.
   1214        for key, values in stats.items():
   1215            if self._display_guests:
   1216                if self.get_gname_from_pid(key):
   1217                    total += values.value
   1218                continue
   1219            if not key.find(' ') != -1:
   1220                total += values.value
   1221            else:
   1222                ctotal += values.value
   1223        if total == 0.:
   1224            # we don't have any fields, or all non-child events are filtered
   1225            total = ctotal
   1226
   1227        # print events
   1228        tavg = 0
   1229        tcur = 0
   1230        guest_removed = False
   1231        for key, values in get_sorted_events(self, stats):
   1232            if row >= self.screen.getmaxyx()[0] - 1 or values == (0, 0):
   1233                break
   1234            if self._display_guests:
   1235                key = self.get_gname_from_pid(key)
   1236                if not key:
   1237                    continue
   1238            cur = int(round(values.delta / sleeptime)) if values.delta else 0
   1239            if cur < 0:
   1240                guest_removed = True
   1241                continue
   1242            if key[0] != ' ':
   1243                if values.delta:
   1244                    tcur += values.delta
   1245                ptotal = values.value
   1246                ltotal = total
   1247            else:
   1248                ltotal = ptotal
   1249            self.screen.addstr(row, 1, '%-40s %10d%7.1f %8s' % (key,
   1250                               values.value,
   1251                               values.value * 100 / float(ltotal), cur))
   1252            row += 1
   1253        if row == 3:
   1254            if guest_removed:
   1255                self.screen.addstr(4, 1, 'Guest removed, updating...')
   1256            else:
   1257                self.screen.addstr(4, 1, 'No matching events reported yet')
   1258        if row > 4:
   1259            tavg = int(round(tcur / sleeptime)) if tcur > 0 else ''
   1260            self.screen.addstr(row, 1, '%-40s %10d        %8s' %
   1261                               ('Total', total, tavg), curses.A_BOLD)
   1262        self.screen.refresh()
   1263
   1264    def _display_guest_dead(self):
   1265        marker = '   Guest is DEAD   '
   1266        y = min(len(self._headline), 80 - len(marker))
   1267        self.screen.addstr(0, y, marker, curses.A_BLINK | curses.A_STANDOUT)
   1268
   1269    def _show_msg(self, text):
   1270        """Display message centered text and exit on key press"""
   1271        hint = 'Press any key to continue'
   1272        curses.cbreak()
   1273        self.screen.erase()
   1274        (x, term_width) = self.screen.getmaxyx()
   1275        row = 2
   1276        for line in text:
   1277            start = (term_width - len(line)) // 2
   1278            self.screen.addstr(row, start, line)
   1279            row += 1
   1280        self.screen.addstr(row + 1, (term_width - len(hint)) // 2, hint,
   1281                           curses.A_STANDOUT)
   1282        self.screen.getkey()
   1283
   1284    def _show_help_interactive(self):
   1285        """Display help with list of interactive commands"""
   1286        msg = ('   b     toggle events by guests (debugfs only, honors'
   1287               ' filters)',
   1288               '   c     clear filter',
   1289               '   f     filter by regular expression',
   1290               '   g     filter by guest name/PID',
   1291               '   h     display interactive commands reference',
   1292               '   o     toggle sorting order (Total vs CurAvg/s)',
   1293               '   p     filter by guest name/PID',
   1294               '   q     quit',
   1295               '   r     reset stats',
   1296               '   s     set delay between refreshs (value range: '
   1297               '%s-%s secs)' % (MIN_DELAY, MAX_DELAY),
   1298               '   x     toggle reporting of stats for individual child trace'
   1299               ' events',
   1300               'Any other key refreshes statistics immediately')
   1301        curses.cbreak()
   1302        self.screen.erase()
   1303        self.screen.addstr(0, 0, "Interactive commands reference",
   1304                           curses.A_BOLD)
   1305        self.screen.addstr(2, 0, "Press any key to exit", curses.A_STANDOUT)
   1306        row = 4
   1307        for line in msg:
   1308            self.screen.addstr(row, 0, line)
   1309            row += 1
   1310        self.screen.getkey()
   1311        self._refresh_header()
   1312
   1313    def _show_filter_selection(self):
   1314        """Draws filter selection mask.
   1315
   1316        Asks for a valid regex and sets the fields filter accordingly.
   1317
   1318        """
   1319        msg = ''
   1320        while True:
   1321            self.screen.erase()
   1322            self.screen.addstr(0, 0,
   1323                               "Show statistics for events matching a regex.",
   1324                               curses.A_BOLD)
   1325            self.screen.addstr(2, 0,
   1326                               "Current regex: {0}"
   1327                               .format(self.stats.fields_filter))
   1328            self.screen.addstr(5, 0, msg)
   1329            self.screen.addstr(3, 0, "New regex: ")
   1330            curses.echo()
   1331            regex = self.screen.getstr().decode(ENCODING)
   1332            curses.noecho()
   1333            if len(regex) == 0:
   1334                self.stats.fields_filter = ''
   1335                self._refresh_header()
   1336                return
   1337            try:
   1338                re.compile(regex)
   1339                self.stats.fields_filter = regex
   1340                self._refresh_header()
   1341                return
   1342            except re.error:
   1343                msg = '"' + regex + '": Not a valid regular expression'
   1344                continue
   1345
   1346    def _show_set_update_interval(self):
   1347        """Draws update interval selection mask."""
   1348        msg = ''
   1349        while True:
   1350            self.screen.erase()
   1351            self.screen.addstr(0, 0, 'Set update interval (defaults to %.1fs).'
   1352                               % DELAY_DEFAULT, curses.A_BOLD)
   1353            self.screen.addstr(4, 0, msg)
   1354            self.screen.addstr(2, 0, 'Change delay from %.1fs to ' %
   1355                               self._delay_regular)
   1356            curses.echo()
   1357            val = self.screen.getstr().decode(ENCODING)
   1358            curses.noecho()
   1359
   1360            try:
   1361                if len(val) > 0:
   1362                    delay = float(val)
   1363                    err = is_delay_valid(delay)
   1364                    if err is not None:
   1365                        msg = err
   1366                        continue
   1367                else:
   1368                    delay = DELAY_DEFAULT
   1369                self._delay_regular = delay
   1370                break
   1371
   1372            except ValueError:
   1373                msg = '"' + str(val) + '": Invalid value'
   1374        self._refresh_header()
   1375
   1376    def _is_running_guest(self, pid):
   1377        """Check if pid is still a running process."""
   1378        if not pid:
   1379            return True
   1380        return os.path.isdir(os.path.join('/proc/', str(pid)))
   1381
   1382    def _show_vm_selection_by_guest(self):
   1383        """Draws guest selection mask.
   1384
   1385        Asks for a guest name or pid until a valid guest name or '' is entered.
   1386
   1387        """
   1388        msg = ''
   1389        while True:
   1390            self.screen.erase()
   1391            self.screen.addstr(0, 0,
   1392                               'Show statistics for specific guest or pid.',
   1393                               curses.A_BOLD)
   1394            self.screen.addstr(1, 0,
   1395                               'This might limit the shown data to the trace '
   1396                               'statistics.')
   1397            self.screen.addstr(5, 0, msg)
   1398            self._print_all_gnames(7)
   1399            curses.echo()
   1400            curses.curs_set(1)
   1401            self.screen.addstr(3, 0, "Guest or pid [ENTER exits]: ")
   1402            guest = self.screen.getstr().decode(ENCODING)
   1403            curses.noecho()
   1404
   1405            pid = 0
   1406            if not guest or guest == '0':
   1407                break
   1408            if guest.isdigit():
   1409                if not self._is_running_guest(guest):
   1410                    msg = '"' + guest + '": Not a running process'
   1411                    continue
   1412                pid = int(guest)
   1413                break
   1414            pids = []
   1415            try:
   1416                pids = self.get_pid_from_gname(guest)
   1417            except:
   1418                msg = '"' + guest + '": Internal error while searching, ' \
   1419                      'use pid filter instead'
   1420                continue
   1421            if len(pids) == 0:
   1422                msg = '"' + guest + '": Not an active guest'
   1423                continue
   1424            if len(pids) > 1:
   1425                msg = '"' + guest + '": Multiple matches found, use pid ' \
   1426                      'filter instead'
   1427                continue
   1428            pid = pids[0]
   1429            break
   1430        curses.curs_set(0)
   1431        self._refresh_header(pid)
   1432        self._update_pid(pid)
   1433
   1434    def show_stats(self):
   1435        """Refreshes the screen and processes user input."""
   1436        sleeptime = self._delay_initial
   1437        self._refresh_header()
   1438        start = 0.0  # result based on init value never appears on screen
   1439        while True:
   1440            self._refresh_body(time.time() - start)
   1441            curses.halfdelay(int(sleeptime * 10))
   1442            start = time.time()
   1443            sleeptime = self._delay_regular
   1444            try:
   1445                char = self.screen.getkey()
   1446                if char == 'b':
   1447                    self._display_guests = not self._display_guests
   1448                    if self.stats.toggle_display_guests(self._display_guests):
   1449                        self._show_msg(['Command not available with '
   1450                                        'tracepoints enabled', 'Restart with '
   1451                                        'debugfs only (see option \'-d\') and '
   1452                                        'try again!'])
   1453                        self._display_guests = not self._display_guests
   1454                    self._refresh_header()
   1455                if char == 'c':
   1456                    self.stats.fields_filter = ''
   1457                    self._refresh_header(0)
   1458                    self._update_pid(0)
   1459                if char == 'f':
   1460                    curses.curs_set(1)
   1461                    self._show_filter_selection()
   1462                    curses.curs_set(0)
   1463                    sleeptime = self._delay_initial
   1464                if char == 'g' or char == 'p':
   1465                    self._show_vm_selection_by_guest()
   1466                    sleeptime = self._delay_initial
   1467                if char == 'h':
   1468                    self._show_help_interactive()
   1469                if char == 'o':
   1470                    self._sorting = not self._sorting
   1471                if char == 'q':
   1472                    break
   1473                if char == 'r':
   1474                    self.stats.reset()
   1475                if char == 's':
   1476                    curses.curs_set(1)
   1477                    self._show_set_update_interval()
   1478                    curses.curs_set(0)
   1479                    sleeptime = self._delay_initial
   1480                if char == 'x':
   1481                    self.stats.child_events = not self.stats.child_events
   1482            except KeyboardInterrupt:
   1483                break
   1484            except curses.error:
   1485                continue
   1486
   1487
   1488def batch(stats):
   1489    """Prints statistics in a key, value format."""
   1490    try:
   1491        s = stats.get()
   1492        time.sleep(1)
   1493        s = stats.get()
   1494        for key, values in sorted(s.items()):
   1495            print('%-42s%10d%10d' % (key.split(' ')[0], values.value,
   1496                  values.delta))
   1497    except KeyboardInterrupt:
   1498        pass
   1499
   1500
   1501class StdFormat(object):
   1502    def __init__(self, keys):
   1503        self._banner = ''
   1504        for key in keys:
   1505            self._banner += key.split(' ')[0] + ' '
   1506
   1507    def get_banner(self):
   1508        return self._banner
   1509
   1510    def get_statline(self, keys, s):
   1511        res = ''
   1512        for key in keys:
   1513            res += ' %9d' % s[key].delta
   1514        return res
   1515
   1516
   1517class CSVFormat(object):
   1518    def __init__(self, keys):
   1519        self._banner = 'timestamp'
   1520        self._banner += reduce(lambda res, key: "{},{!s}".format(res,
   1521                               key.split(' ')[0]), keys, '')
   1522
   1523    def get_banner(self):
   1524        return self._banner
   1525
   1526    def get_statline(self, keys, s):
   1527        return reduce(lambda res, key: "{},{!s}".format(res, s[key].delta),
   1528                      keys, '')
   1529
   1530
   1531def log(stats, opts, frmt, keys):
   1532    """Prints statistics as reiterating key block, multiple value blocks."""
   1533    global signal_received
   1534    line = 0
   1535    banner_repeat = 20
   1536    f = None
   1537
   1538    def do_banner(opts):
   1539        nonlocal f
   1540        if opts.log_to_file:
   1541            if not f:
   1542                try:
   1543                     f = open(opts.log_to_file, 'a')
   1544                except (IOError, OSError):
   1545                    sys.exit("Error: Could not open file: %s" %
   1546                             opts.log_to_file)
   1547                if isinstance(frmt, CSVFormat) and f.tell() != 0:
   1548                    return
   1549        print(frmt.get_banner(), file=f or sys.stdout)
   1550
   1551    def do_statline(opts, values):
   1552        statline = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + \
   1553                   frmt.get_statline(keys, values)
   1554        print(statline, file=f or sys.stdout)
   1555
   1556    do_banner(opts)
   1557    banner_printed = True
   1558    while True:
   1559        try:
   1560            time.sleep(opts.set_delay)
   1561            if signal_received:
   1562                banner_printed = True
   1563                line = 0
   1564                f.close()
   1565                do_banner(opts)
   1566                signal_received = False
   1567            if (line % banner_repeat == 0 and not banner_printed and
   1568                not (opts.log_to_file and isinstance(frmt, CSVFormat))):
   1569                do_banner(opts)
   1570                banner_printed = True
   1571            values = stats.get()
   1572            if (not opts.skip_zero_records or
   1573                any(values[k].delta != 0 for k in keys)):
   1574                do_statline(opts, values)
   1575                line += 1
   1576                banner_printed = False
   1577        except KeyboardInterrupt:
   1578            break
   1579
   1580    if opts.log_to_file:
   1581        f.close()
   1582
   1583
   1584def handle_signal(sig, frame):
   1585    global signal_received
   1586
   1587    signal_received = True
   1588
   1589    return
   1590
   1591
   1592def is_delay_valid(delay):
   1593    """Verify delay is in valid value range."""
   1594    msg = None
   1595    if delay < MIN_DELAY:
   1596        msg = '"' + str(delay) + '": Delay must be >=%s' % MIN_DELAY
   1597    if delay > MAX_DELAY:
   1598        msg = '"' + str(delay) + '": Delay must be <=%s' % MAX_DELAY
   1599    return msg
   1600
   1601
   1602def get_options():
   1603    """Returns processed program arguments."""
   1604    description_text = """
   1605This script displays various statistics about VMs running under KVM.
   1606The statistics are gathered from the KVM debugfs entries and / or the
   1607currently available perf traces.
   1608
   1609The monitoring takes additional cpu cycles and might affect the VM's
   1610performance.
   1611
   1612Requirements:
   1613- Access to:
   1614    %s
   1615    %s/events/*
   1616    /proc/pid/task
   1617- /proc/sys/kernel/perf_event_paranoid < 1 if user has no
   1618  CAP_SYS_ADMIN and perf events are used.
   1619- CAP_SYS_RESOURCE if the hard limit is not high enough to allow
   1620  the large number of files that are possibly opened.
   1621
   1622Interactive Commands:
   1623   b     toggle events by guests (debugfs only, honors filters)
   1624   c     clear filter
   1625   f     filter by regular expression
   1626   g     filter by guest name
   1627   h     display interactive commands reference
   1628   o     toggle sorting order (Total vs CurAvg/s)
   1629   p     filter by PID
   1630   q     quit
   1631   r     reset stats
   1632   s     set update interval (value range: 0.1-25.5 secs)
   1633   x     toggle reporting of stats for individual child trace events
   1634Press any other key to refresh statistics immediately.
   1635""" % (PATH_DEBUGFS_KVM, PATH_DEBUGFS_TRACING)
   1636
   1637    class Guest_to_pid(argparse.Action):
   1638        def __call__(self, parser, namespace, values, option_string=None):
   1639            try:
   1640                pids = Tui.get_pid_from_gname(values)
   1641            except:
   1642                sys.exit('Error while searching for guest "{}". Use "-p" to '
   1643                         'specify a pid instead?'.format(values))
   1644            if len(pids) == 0:
   1645                sys.exit('Error: No guest by the name "{}" found'
   1646                         .format(values))
   1647            if len(pids) > 1:
   1648                sys.exit('Error: Multiple processes found (pids: {}). Use "-p"'
   1649                         ' to specify the desired pid'
   1650                         .format(" ".join(map(str, pids))))
   1651            namespace.pid = pids[0]
   1652
   1653    argparser = argparse.ArgumentParser(description=description_text,
   1654                                        formatter_class=argparse
   1655                                        .RawTextHelpFormatter)
   1656    argparser.add_argument('-1', '--once', '--batch',
   1657                           action='store_true',
   1658                           default=False,
   1659                           help='run in batch mode for one second',
   1660                           )
   1661    argparser.add_argument('-c', '--csv',
   1662                           action='store_true',
   1663                           default=False,
   1664                           help='log in csv format - requires option -l/-L',
   1665                           )
   1666    argparser.add_argument('-d', '--debugfs',
   1667                           action='store_true',
   1668                           default=False,
   1669                           help='retrieve statistics from debugfs',
   1670                           )
   1671    argparser.add_argument('-f', '--fields',
   1672                           default='',
   1673                           help='''fields to display (regex)
   1674"-f help" for a list of available events''',
   1675                           )
   1676    argparser.add_argument('-g', '--guest',
   1677                           type=str,
   1678                           help='restrict statistics to guest by name',
   1679                           action=Guest_to_pid,
   1680                           )
   1681    argparser.add_argument('-i', '--debugfs-include-past',
   1682                           action='store_true',
   1683                           default=False,
   1684                           help='include all available data on past events for'
   1685                                ' debugfs',
   1686                           )
   1687    argparser.add_argument('-l', '--log',
   1688                           action='store_true',
   1689                           default=False,
   1690                           help='run in logging mode (like vmstat)',
   1691                           )
   1692    argparser.add_argument('-L', '--log-to-file',
   1693                           type=str,
   1694                           metavar='FILE',
   1695                           help="like '--log', but logging to a file"
   1696                           )
   1697    argparser.add_argument('-p', '--pid',
   1698                           type=int,
   1699                           default=0,
   1700                           help='restrict statistics to pid',
   1701                           )
   1702    argparser.add_argument('-s', '--set-delay',
   1703                           type=float,
   1704                           default=DELAY_DEFAULT,
   1705                           metavar='DELAY',
   1706                           help='set delay between refreshs (value range: '
   1707                                '%s-%s secs)' % (MIN_DELAY, MAX_DELAY),
   1708                           )
   1709    argparser.add_argument('-t', '--tracepoints',
   1710                           action='store_true',
   1711                           default=False,
   1712                           help='retrieve statistics from tracepoints',
   1713                           )
   1714    argparser.add_argument('-z', '--skip-zero-records',
   1715                           action='store_true',
   1716                           default=False,
   1717                           help='omit records with all zeros in logging mode',
   1718                           )
   1719    options = argparser.parse_args()
   1720    if options.csv and not (options.log or options.log_to_file):
   1721        sys.exit('Error: Option -c/--csv requires -l/--log')
   1722    if options.skip_zero_records and not (options.log or options.log_to_file):
   1723        sys.exit('Error: Option -z/--skip-zero-records requires -l/-L')
   1724    try:
   1725        # verify that we were passed a valid regex up front
   1726        re.compile(options.fields)
   1727    except re.error:
   1728        sys.exit('Error: "' + options.fields + '" is not a valid regular '
   1729                 'expression')
   1730
   1731    return options
   1732
   1733
   1734def check_access(options):
   1735    """Exits if the current user can't access all needed directories."""
   1736    if not os.path.exists(PATH_DEBUGFS_TRACING) and (options.tracepoints or
   1737                                                     not options.debugfs):
   1738        sys.stderr.write("Please enable CONFIG_TRACING in your kernel "
   1739                         "when using the option -t (default).\n"
   1740                         "If it is enabled, make {0} readable by the "
   1741                         "current user.\n"
   1742                         .format(PATH_DEBUGFS_TRACING))
   1743        if options.tracepoints:
   1744            sys.exit(1)
   1745
   1746        sys.stderr.write("Falling back to debugfs statistics!\n")
   1747        options.debugfs = True
   1748        time.sleep(5)
   1749
   1750    return options
   1751
   1752
   1753def assign_globals():
   1754    global PATH_DEBUGFS_KVM
   1755    global PATH_DEBUGFS_TRACING
   1756
   1757    debugfs = ''
   1758    for line in open('/proc/mounts'):
   1759        if line.split(' ')[0] == 'debugfs':
   1760            debugfs = line.split(' ')[1]
   1761            break
   1762    if debugfs == '':
   1763        sys.stderr.write("Please make sure that CONFIG_DEBUG_FS is enabled in "
   1764                         "your kernel, mounted and\nreadable by the current "
   1765                         "user:\n"
   1766                         "('mount -t debugfs debugfs /sys/kernel/debug')\n")
   1767        sys.exit(1)
   1768
   1769    PATH_DEBUGFS_KVM = os.path.join(debugfs, 'kvm')
   1770    PATH_DEBUGFS_TRACING = os.path.join(debugfs, 'tracing')
   1771
   1772    if not os.path.exists(PATH_DEBUGFS_KVM):
   1773        sys.stderr.write("Please make sure that CONFIG_KVM is enabled in "
   1774                         "your kernel and that the modules are loaded.\n")
   1775        sys.exit(1)
   1776
   1777
   1778def main():
   1779    assign_globals()
   1780    options = get_options()
   1781    options = check_access(options)
   1782
   1783    if (options.pid > 0 and
   1784        not os.path.isdir(os.path.join('/proc/',
   1785                                       str(options.pid)))):
   1786        sys.stderr.write('Did you use a (unsupported) tid instead of a pid?\n')
   1787        sys.exit('Specified pid does not exist.')
   1788
   1789    err = is_delay_valid(options.set_delay)
   1790    if err is not None:
   1791        sys.exit('Error: ' + err)
   1792
   1793    stats = Stats(options)
   1794
   1795    if options.fields == 'help':
   1796        stats.fields_filter = None
   1797        event_list = []
   1798        for key in stats.get().keys():
   1799            event_list.append(key.split('(', 1)[0])
   1800        sys.stdout.write('  ' + '\n  '.join(sorted(set(event_list))) + '\n')
   1801        sys.exit(0)
   1802
   1803    if options.log or options.log_to_file:
   1804        if options.log_to_file:
   1805            signal.signal(signal.SIGHUP, handle_signal)
   1806        keys = sorted(stats.get().keys())
   1807        if options.csv:
   1808            frmt = CSVFormat(keys)
   1809        else:
   1810            frmt = StdFormat(keys)
   1811        log(stats, options, frmt, keys)
   1812    elif not options.once:
   1813        with Tui(stats, options) as tui:
   1814            tui.show_stats()
   1815    else:
   1816        batch(stats)
   1817
   1818
   1819if __name__ == "__main__":
   1820    main()