Blame SOURCES/0006-Revert-kvm_stat-Remove.patch

357786
From 49ebd26734d492cc4189b97704052780b3641854 Mon Sep 17 00:00:00 2001
9bac43
From: "Danilo C. L. de Paula" <ddepaula@redhat.com>
9bac43
Date: Mon, 16 Jan 2017 11:52:49 +0100
9bac43
Subject: Revert "kvm_stat: Remove"
9bac43
9bac43
RH-Author: ddepaula <ddepaula@redhat.com>
9bac43
Message-id: <1479302806-10135-2-git-send-email-ddepaula@redhat.com>
9bac43
Patchwork-id: 72851
9bac43
O-Subject: [RHEV-7.4 qemu-kvm-rhev PATCH v3 1/3] Revert "kvm_stat: Remove"
9bac43
Bugzilla: 1389238
9bac43
RH-Acked-by: John Snow <jsnow@redhat.com>
9bac43
RH-Acked-by: David Hildenbrand <david@redhat.com>
9bac43
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
9bac43
9bac43
kvm_stat script was removed in QEMU 2.7.0 as it become part of kernel
9bac43
tree. However kvm_stat is shipped in qemu-kvm-tools package in RHEL.
9bac43
9bac43
This reverts commit 60b412dd18362bd4ddc44ba7022aacb6af074b5d.
9bac43
9bac43
Signed-off-by: Danilo Cesar Lemes de Paula <ddepaula@redhat.com>
9bac43
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
9bac43
357786
Merged patches (2.11.0):
357786
- beffa30342 tools/kvm_stat: hide cursor
357786
- 98cf56caaa tools/kvm_stat: catch curses exceptions only
357786
- dada85e20a tools/kvm_stat: handle SIGINT in log and batch modes
357786
- 8654f68785 tools/kvm_stat: fix misc glitches
357786
- d4525d7460 tools/kvm_stat: fix trace setup glitch on field updates in TracepointProvider
357786
- 5f41d9716a tools/kvm_stat: full PEP8 compliance
357786
- 5bf653923c tools/kvm_stat: reduce perceived idle time on filter updates
357786
- 2ef292f12d tools/kvm_stat: document list of interactive commands
357786
- 3e1d2fc34c tools/kvm_stat: display guest name when using pid filter
357786
- 66f2e8203e tools/kvm_stat: remove pid filter on empty input
357786
- 05cc1c1aab tools/kvm_stat: print error messages on faulty pid filter input
357786
- a0c6451760 tools/kvm_stat: display regex when set to non-default
357786
- 3c7dae7507 tools/kvm_stat: remove regex filter on empty input
357786
- 8b7b31fc24 tools/kvm_stat: add option '--guest'
357786
- 38362f9d75 tools/kvm_stat: add interactive command 'c'
357786
- 243d90a041 tools/kvm_stat: add interactive command 'r'
357786
- 4f80c9081b tools/kvm_stat: add '%Total' column
357786
- e21fb4a96e tools/kvm_stat: fix typo
357786
- 7bc419ee18 tools/kvm_stat: fix event counts display for interrupted intervals
357786
- d54183dd4b tools/kvm_stat: fix undue use of initial sleeptime
357786
- 6514729674 tools/kvm_stat: remove unnecessary header redraws
357786
- c4f941df75 tools/kvm_stat: simplify line print logic
357786
- 35c050e4fd tools/kvm_stat: removed unused function
357786
- 92d06e843c tools/kvm_stat: remove extra statement
357786
- d0ced131bf tools/kvm_stat: simplify initializers
357786
- 1dbc0659ac tools/kvm_stat: move functions to corresponding classes
357786
- bf53d77372 tools/kvm_stat: show cursor in selection screens
357786
- 6b557dc235 tools/kvm_stat: display message indicating lack of events
357786
- 7f50a8be62 tools/kvm_stat: make heading look a bit more like 'top'
357786
- 8ba2fd89d1 tools/kvm_stat: rename 'Current' column to 'CurAvg/s'
357786
- 95b5c46145 tools/kvm_stat: add new interactive command 'h'
357786
- 25c56e3212 tools/kvm_stat: add new interactive command 's'
357786
- 55f7ed9b2c tools/kvm_stat: add new interactive command 'o'
357786
- 53a1267b00 tools/kvm_stat: display guest list in pid/guest selection screens
357786
- 554fa10df5 tools/kvm_stat: display guest list in pid/guest selection screens
357786
- 72f660ddfc tools/kvm_stat: add new command line switch '-i'
357786
- 17165a0bc8 tools/kvm_stat: add new interactive command 'b'
357786
- ac6ba9a14b tools/kvm_stat: use variables instead of hard paths in help output
357786
- 592b801e5c tools/kvm_stat: add '-f help' to get the available event list
357786
- 283aa25f75 tools/kvm_stat: fix command line option '-g'
357786
9bac43
Merged patches (2.9.0):
9bac43
- 1e69b1b Include kvm_stat in qemu-kvm.spec
9bac43
- 7fcfc94 tools: kvm_stat: Powerpc related fixes
9bac43
- 7f89136 tools: kvm_stat: Introduce pid monitoring
9bac43
- c728a6b tools: kvm_stat: Add comments
9bac43
- 27fb856 Package man page of "kvm_stat" tool
9bac43
9bac43
(cherry picked from commit d4a8e35b84072816c79e23f5d0a69a2145217004)
357786
(cherry picked from commit e0425f69f136a05a59ee5cb7022409ed2be94a0b)
357786
(cherry picked from commit 57e760aee8b28f3b2c6b9f3aad0d2b916dd13595)
357786
(cherry picked from commit d711afa1b8ef8aba1397158626816008936ed0d6)
357786
(cherry picked from commit 8ceca7bebce67440898c22d058047daa8ec37031)
9bac43
---
9bac43
 Makefile                      |    8 +
9bac43
 redhat/qemu-kvm.spec.template |    7 +-
357786
 scripts/kvm/kvm_stat          | 1585 +++++++++++++++++++++++++++++++++++++++++
357786
 scripts/kvm/kvm_stat.texi     |  104 +++
357786
 4 files changed, 1703 insertions(+), 1 deletion(-)
9bac43
 create mode 100755 scripts/kvm/kvm_stat
9bac43
 create mode 100644 scripts/kvm/kvm_stat.texi
9bac43
9bac43
diff --git a/Makefile b/Makefile
357786
index 89ba4c5..2ffac2b 100644
9bac43
--- a/Makefile
9bac43
+++ b/Makefile
357786
@@ -354,6 +354,9 @@ DOCS=qemu-doc.html qemu-doc.txt qemu.1 qemu-img.1 qemu-nbd.8 qemu-ga.8
9bac43
 DOCS+=docs/interop/qemu-qmp-ref.html docs/interop/qemu-qmp-ref.txt docs/interop/qemu-qmp-ref.7
9bac43
 DOCS+=docs/interop/qemu-ga-ref.html docs/interop/qemu-ga-ref.txt docs/interop/qemu-ga-ref.7
357786
 DOCS+=docs/qemu-block-drivers.7
9bac43
+ifdef CONFIG_LINUX
9bac43
+DOCS+=kvm_stat.1
9bac43
+endif
9bac43
 ifdef CONFIG_VIRTFS
9bac43
 DOCS+=fsdev/virtfs-proxy-helper.1
9bac43
 endif
357786
@@ -955,6 +958,11 @@ html: qemu-doc.html docs/interop/qemu-qmp-ref.html docs/interop/qemu-ga-ref.html
9bac43
 info: qemu-doc.info docs/interop/qemu-qmp-ref.info docs/interop/qemu-ga-ref.info
9bac43
 pdf: qemu-doc.pdf docs/interop/qemu-qmp-ref.pdf docs/interop/qemu-ga-ref.pdf
9bac43
 txt: qemu-doc.txt docs/interop/qemu-qmp-ref.txt docs/interop/qemu-ga-ref.txt
9bac43
+kvm_stat.1: scripts/kvm/kvm_stat.texi
9bac43
+	$(call quiet-command, \
9bac43
+	  perl -Ww -- $(SRC_PATH)/scripts/texi2pod.pl $< kvm_stat.pod && \
9bac43
+	  $(POD2MAN) --section=1 --center=" " --release=" " kvm_stat.pod > $@, \
9bac43
+	  "  GEN   $@")
9bac43
 
9bac43
 qemu-doc.html qemu-doc.info qemu-doc.pdf qemu-doc.txt: \
9bac43
 	qemu-img.texi qemu-nbd.texi qemu-options.texi qemu-option-trace.texi \
9bac43
diff --git a/scripts/kvm/kvm_stat b/scripts/kvm/kvm_stat
9bac43
new file mode 100755
357786
index 0000000..c74a9a0
9bac43
--- /dev/null
9bac43
+++ b/scripts/kvm/kvm_stat
357786
@@ -0,0 +1,1585 @@
9bac43
+#!/usr/bin/python
9bac43
+#
9bac43
+# top-like utility for displaying kvm statistics
9bac43
+#
9bac43
+# Copyright 2006-2008 Qumranet Technologies
9bac43
+# Copyright 2008-2011 Red Hat, Inc.
9bac43
+#
9bac43
+# Authors:
9bac43
+#  Avi Kivity <avi@redhat.com>
9bac43
+#
9bac43
+# This work is licensed under the terms of the GNU GPL, version 2.  See
9bac43
+# the COPYING file in the top-level directory.
9bac43
+"""The kvm_stat module outputs statistics about running KVM VMs
9bac43
+
9bac43
+Three different ways of output formatting are available:
9bac43
+- as a top-like text ui
9bac43
+- in a key -> value format
9bac43
+- in an all keys, all values format
9bac43
+
9bac43
+The data is sampled from the KVM's debugfs entries and its perf events.
9bac43
+"""
9bac43
+
9bac43
+import curses
9bac43
+import sys
9bac43
+import os
9bac43
+import time
9bac43
+import optparse
9bac43
+import ctypes
9bac43
+import fcntl
9bac43
+import resource
9bac43
+import struct
9bac43
+import re
357786
+import subprocess
9bac43
+from collections import defaultdict
9bac43
+
9bac43
+VMX_EXIT_REASONS = {
9bac43
+    'EXCEPTION_NMI':        0,
9bac43
+    'EXTERNAL_INTERRUPT':   1,
9bac43
+    'TRIPLE_FAULT':         2,
9bac43
+    'PENDING_INTERRUPT':    7,
9bac43
+    'NMI_WINDOW':           8,
9bac43
+    'TASK_SWITCH':          9,
9bac43
+    'CPUID':                10,
9bac43
+    'HLT':                  12,
9bac43
+    'INVLPG':               14,
9bac43
+    'RDPMC':                15,
9bac43
+    'RDTSC':                16,
9bac43
+    'VMCALL':               18,
9bac43
+    'VMCLEAR':              19,
9bac43
+    'VMLAUNCH':             20,
9bac43
+    'VMPTRLD':              21,
9bac43
+    'VMPTRST':              22,
9bac43
+    'VMREAD':               23,
9bac43
+    'VMRESUME':             24,
9bac43
+    'VMWRITE':              25,
9bac43
+    'VMOFF':                26,
9bac43
+    'VMON':                 27,
9bac43
+    'CR_ACCESS':            28,
9bac43
+    'DR_ACCESS':            29,
9bac43
+    'IO_INSTRUCTION':       30,
9bac43
+    'MSR_READ':             31,
9bac43
+    'MSR_WRITE':            32,
9bac43
+    'INVALID_STATE':        33,
9bac43
+    'MWAIT_INSTRUCTION':    36,
9bac43
+    'MONITOR_INSTRUCTION':  39,
9bac43
+    'PAUSE_INSTRUCTION':    40,
9bac43
+    'MCE_DURING_VMENTRY':   41,
9bac43
+    'TPR_BELOW_THRESHOLD':  43,
9bac43
+    'APIC_ACCESS':          44,
9bac43
+    'EPT_VIOLATION':        48,
9bac43
+    'EPT_MISCONFIG':        49,
9bac43
+    'WBINVD':               54,
9bac43
+    'XSETBV':               55,
9bac43
+    'APIC_WRITE':           56,
9bac43
+    'INVPCID':              58,
9bac43
+}
9bac43
+
9bac43
+SVM_EXIT_REASONS = {
9bac43
+    'READ_CR0':       0x000,
9bac43
+    'READ_CR3':       0x003,
9bac43
+    'READ_CR4':       0x004,
9bac43
+    'READ_CR8':       0x008,
9bac43
+    'WRITE_CR0':      0x010,
9bac43
+    'WRITE_CR3':      0x013,
9bac43
+    'WRITE_CR4':      0x014,
9bac43
+    'WRITE_CR8':      0x018,
9bac43
+    'READ_DR0':       0x020,
9bac43
+    'READ_DR1':       0x021,
9bac43
+    'READ_DR2':       0x022,
9bac43
+    'READ_DR3':       0x023,
9bac43
+    'READ_DR4':       0x024,
9bac43
+    'READ_DR5':       0x025,
9bac43
+    'READ_DR6':       0x026,
9bac43
+    'READ_DR7':       0x027,
9bac43
+    'WRITE_DR0':      0x030,
9bac43
+    'WRITE_DR1':      0x031,
9bac43
+    'WRITE_DR2':      0x032,
9bac43
+    'WRITE_DR3':      0x033,
9bac43
+    'WRITE_DR4':      0x034,
9bac43
+    'WRITE_DR5':      0x035,
9bac43
+    'WRITE_DR6':      0x036,
9bac43
+    'WRITE_DR7':      0x037,
9bac43
+    'EXCP_BASE':      0x040,
9bac43
+    'INTR':           0x060,
9bac43
+    'NMI':            0x061,
9bac43
+    'SMI':            0x062,
9bac43
+    'INIT':           0x063,
9bac43
+    'VINTR':          0x064,
9bac43
+    'CR0_SEL_WRITE':  0x065,
9bac43
+    'IDTR_READ':      0x066,
9bac43
+    'GDTR_READ':      0x067,
9bac43
+    'LDTR_READ':      0x068,
9bac43
+    'TR_READ':        0x069,
9bac43
+    'IDTR_WRITE':     0x06a,
9bac43
+    'GDTR_WRITE':     0x06b,
9bac43
+    'LDTR_WRITE':     0x06c,
9bac43
+    'TR_WRITE':       0x06d,
9bac43
+    'RDTSC':          0x06e,
9bac43
+    'RDPMC':          0x06f,
9bac43
+    'PUSHF':          0x070,
9bac43
+    'POPF':           0x071,
9bac43
+    'CPUID':          0x072,
9bac43
+    'RSM':            0x073,
9bac43
+    'IRET':           0x074,
9bac43
+    'SWINT':          0x075,
9bac43
+    'INVD':           0x076,
9bac43
+    'PAUSE':          0x077,
9bac43
+    'HLT':            0x078,
9bac43
+    'INVLPG':         0x079,
9bac43
+    'INVLPGA':        0x07a,
9bac43
+    'IOIO':           0x07b,
9bac43
+    'MSR':            0x07c,
9bac43
+    'TASK_SWITCH':    0x07d,
9bac43
+    'FERR_FREEZE':    0x07e,
9bac43
+    'SHUTDOWN':       0x07f,
9bac43
+    'VMRUN':          0x080,
9bac43
+    'VMMCALL':        0x081,
9bac43
+    'VMLOAD':         0x082,
9bac43
+    'VMSAVE':         0x083,
9bac43
+    'STGI':           0x084,
9bac43
+    'CLGI':           0x085,
9bac43
+    'SKINIT':         0x086,
9bac43
+    'RDTSCP':         0x087,
9bac43
+    'ICEBP':          0x088,
9bac43
+    'WBINVD':         0x089,
9bac43
+    'MONITOR':        0x08a,
9bac43
+    'MWAIT':          0x08b,
9bac43
+    'MWAIT_COND':     0x08c,
9bac43
+    'XSETBV':         0x08d,
9bac43
+    'NPF':            0x400,
9bac43
+}
9bac43
+
9bac43
+# EC definition of HSR (from arch/arm64/include/asm/kvm_arm.h)
9bac43
+AARCH64_EXIT_REASONS = {
9bac43
+    'UNKNOWN':      0x00,
9bac43
+    'WFI':          0x01,
9bac43
+    'CP15_32':      0x03,
9bac43
+    'CP15_64':      0x04,
9bac43
+    'CP14_MR':      0x05,
9bac43
+    'CP14_LS':      0x06,
9bac43
+    'FP_ASIMD':     0x07,
9bac43
+    'CP10_ID':      0x08,
9bac43
+    'CP14_64':      0x0C,
9bac43
+    'ILL_ISS':      0x0E,
9bac43
+    'SVC32':        0x11,
9bac43
+    'HVC32':        0x12,
9bac43
+    'SMC32':        0x13,
9bac43
+    'SVC64':        0x15,
9bac43
+    'HVC64':        0x16,
9bac43
+    'SMC64':        0x17,
9bac43
+    'SYS64':        0x18,
9bac43
+    'IABT':         0x20,
9bac43
+    'IABT_HYP':     0x21,
9bac43
+    'PC_ALIGN':     0x22,
9bac43
+    'DABT':         0x24,
9bac43
+    'DABT_HYP':     0x25,
9bac43
+    'SP_ALIGN':     0x26,
9bac43
+    'FP_EXC32':     0x28,
9bac43
+    'FP_EXC64':     0x2C,
9bac43
+    'SERROR':       0x2F,
9bac43
+    'BREAKPT':      0x30,
9bac43
+    'BREAKPT_HYP':  0x31,
9bac43
+    'SOFTSTP':      0x32,
9bac43
+    'SOFTSTP_HYP':  0x33,
9bac43
+    'WATCHPT':      0x34,
9bac43
+    'WATCHPT_HYP':  0x35,
9bac43
+    'BKPT32':       0x38,
9bac43
+    'VECTOR32':     0x3A,
9bac43
+    'BRK64':        0x3C,
9bac43
+}
9bac43
+
9bac43
+# From include/uapi/linux/kvm.h, KVM_EXIT_xxx
9bac43
+USERSPACE_EXIT_REASONS = {
9bac43
+    'UNKNOWN':          0,
9bac43
+    'EXCEPTION':        1,
9bac43
+    'IO':               2,
9bac43
+    'HYPERCALL':        3,
9bac43
+    'DEBUG':            4,
9bac43
+    'HLT':              5,
9bac43
+    'MMIO':             6,
9bac43
+    'IRQ_WINDOW_OPEN':  7,
9bac43
+    'SHUTDOWN':         8,
9bac43
+    'FAIL_ENTRY':       9,
9bac43
+    'INTR':             10,
9bac43
+    'SET_TPR':          11,
9bac43
+    'TPR_ACCESS':       12,
9bac43
+    'S390_SIEIC':       13,
9bac43
+    'S390_RESET':       14,
9bac43
+    'DCR':              15,
9bac43
+    'NMI':              16,
9bac43
+    'INTERNAL_ERROR':   17,
9bac43
+    'OSI':              18,
9bac43
+    'PAPR_HCALL':       19,
9bac43
+    'S390_UCONTROL':    20,
9bac43
+    'WATCHDOG':         21,
9bac43
+    'S390_TSCH':        22,
9bac43
+    'EPR':              23,
9bac43
+    'SYSTEM_EVENT':     24,
9bac43
+}
9bac43
+
9bac43
+IOCTL_NUMBERS = {
9bac43
+    'SET_FILTER':  0x40082406,
9bac43
+    'ENABLE':      0x00002400,
9bac43
+    'DISABLE':     0x00002401,
9bac43
+    'RESET':       0x00002403,
9bac43
+}
9bac43
+
357786
+
9bac43
+class Arch(object):
9bac43
+    """Encapsulates global architecture specific data.
9bac43
+
9bac43
+    Contains the performance event open syscall and ioctl numbers, as
9bac43
+    well as the VM exit reasons for the architecture it runs on.
9bac43
+
9bac43
+    """
9bac43
+    @staticmethod
9bac43
+    def get_arch():
9bac43
+        machine = os.uname()[4]
9bac43
+
9bac43
+        if machine.startswith('ppc'):
9bac43
+            return ArchPPC()
9bac43
+        elif machine.startswith('aarch64'):
9bac43
+            return ArchA64()
9bac43
+        elif machine.startswith('s390'):
9bac43
+            return ArchS390()
9bac43
+        else:
9bac43
+            # X86_64
9bac43
+            for line in open('/proc/cpuinfo'):
9bac43
+                if not line.startswith('flags'):
9bac43
+                    continue
9bac43
+
9bac43
+                flags = line.split()
9bac43
+                if 'vmx' in flags:
9bac43
+                    return ArchX86(VMX_EXIT_REASONS)
9bac43
+                if 'svm' in flags:
9bac43
+                    return ArchX86(SVM_EXIT_REASONS)
9bac43
+                return
9bac43
+
357786
+
9bac43
+class ArchX86(Arch):
9bac43
+    def __init__(self, exit_reasons):
9bac43
+        self.sc_perf_evt_open = 298
9bac43
+        self.ioctl_numbers = IOCTL_NUMBERS
9bac43
+        self.exit_reasons = exit_reasons
9bac43
+
357786
+
9bac43
+class ArchPPC(Arch):
9bac43
+    def __init__(self):
9bac43
+        self.sc_perf_evt_open = 319
9bac43
+        self.ioctl_numbers = IOCTL_NUMBERS
9bac43
+        self.ioctl_numbers['ENABLE'] = 0x20002400
9bac43
+        self.ioctl_numbers['DISABLE'] = 0x20002401
9bac43
+        self.ioctl_numbers['RESET'] = 0x20002403
9bac43
+
9bac43
+        # PPC comes in 32 and 64 bit and some generated ioctl
9bac43
+        # numbers depend on the wordsize.
9bac43
+        char_ptr_size = ctypes.sizeof(ctypes.c_char_p)
9bac43
+        self.ioctl_numbers['SET_FILTER'] = 0x80002406 | char_ptr_size << 16
9bac43
+        self.exit_reasons = {}
9bac43
+
357786
+
9bac43
+class ArchA64(Arch):
9bac43
+    def __init__(self):
9bac43
+        self.sc_perf_evt_open = 241
9bac43
+        self.ioctl_numbers = IOCTL_NUMBERS
9bac43
+        self.exit_reasons = AARCH64_EXIT_REASONS
9bac43
+
357786
+
9bac43
+class ArchS390(Arch):
9bac43
+    def __init__(self):
9bac43
+        self.sc_perf_evt_open = 331
9bac43
+        self.ioctl_numbers = IOCTL_NUMBERS
9bac43
+        self.exit_reasons = None
9bac43
+
9bac43
+ARCH = Arch.get_arch()
9bac43
+
9bac43
+
9bac43
+class perf_event_attr(ctypes.Structure):
9bac43
+    """Struct that holds the necessary data to set up a trace event.
9bac43
+
9bac43
+    For an extensive explanation see perf_event_open(2) and
9bac43
+    include/uapi/linux/perf_event.h, struct perf_event_attr
9bac43
+
9bac43
+    All fields that are not initialized in the constructor are 0.
9bac43
+
9bac43
+    """
9bac43
+    _fields_ = [('type', ctypes.c_uint32),
9bac43
+                ('size', ctypes.c_uint32),
9bac43
+                ('config', ctypes.c_uint64),
9bac43
+                ('sample_freq', ctypes.c_uint64),
9bac43
+                ('sample_type', ctypes.c_uint64),
9bac43
+                ('read_format', ctypes.c_uint64),
9bac43
+                ('flags', ctypes.c_uint64),
9bac43
+                ('wakeup_events', ctypes.c_uint32),
9bac43
+                ('bp_type', ctypes.c_uint32),
9bac43
+                ('bp_addr', ctypes.c_uint64),
9bac43
+                ('bp_len', ctypes.c_uint64),
9bac43
+                ]
9bac43
+
9bac43
+    def __init__(self):
9bac43
+        super(self.__class__, self).__init__()
9bac43
+        self.type = PERF_TYPE_TRACEPOINT
9bac43
+        self.size = ctypes.sizeof(self)
9bac43
+        self.read_format = PERF_FORMAT_GROUP
9bac43
+
9bac43
+
9bac43
+PERF_TYPE_TRACEPOINT = 2
9bac43
+PERF_FORMAT_GROUP = 1 << 3
9bac43
+
9bac43
+PATH_DEBUGFS_TRACING = '/sys/kernel/debug/tracing'
9bac43
+PATH_DEBUGFS_KVM = '/sys/kernel/debug/kvm'
9bac43
+
357786
+
9bac43
+class Group(object):
9bac43
+    """Represents a perf event group."""
9bac43
+
9bac43
+    def __init__(self):
9bac43
+        self.events = []
9bac43
+
9bac43
+    def add_event(self, event):
9bac43
+        self.events.append(event)
9bac43
+
9bac43
+    def read(self):
9bac43
+        """Returns a dict with 'event name: value' for all events in the
9bac43
+        group.
9bac43
+
9bac43
+        Values are read by reading from the file descriptor of the
9bac43
+        event that is the group leader. See perf_event_open(2) for
9bac43
+        details.
9bac43
+
9bac43
+        Read format for the used event configuration is:
9bac43
+        struct read_format {
9bac43
+            u64 nr; /* The number of events */
9bac43
+            struct {
9bac43
+                u64 value; /* The value of the event */
9bac43
+            } values[nr];
9bac43
+        };
9bac43
+
9bac43
+        """
9bac43
+        length = 8 * (1 + len(self.events))
9bac43
+        read_format = 'xxxxxxxx' + 'Q' * len(self.events)
9bac43
+        return dict(zip([event.name for event in self.events],
9bac43
+                        struct.unpack(read_format,
9bac43
+                                      os.read(self.events[0].fd, length))))
9bac43
+
357786
+
9bac43
+class Event(object):
9bac43
+    """Represents a performance event and manages its life cycle."""
9bac43
+    def __init__(self, name, group, trace_cpu, trace_pid, trace_point,
9bac43
+                 trace_filter, trace_set='kvm'):
357786
+        self.libc = ctypes.CDLL('libc.so.6', use_errno=True)
357786
+        self.syscall = self.libc.syscall
9bac43
+        self.name = name
9bac43
+        self.fd = None
9bac43
+        self.setup_event(group, trace_cpu, trace_pid, trace_point,
9bac43
+                         trace_filter, trace_set)
9bac43
+
9bac43
+    def __del__(self):
9bac43
+        """Closes the event's file descriptor.
9bac43
+
9bac43
+        As no python file object was created for the file descriptor,
9bac43
+        python will not reference count the descriptor and will not
9bac43
+        close it itself automatically, so we do it.
9bac43
+
9bac43
+        """
9bac43
+        if self.fd:
9bac43
+            os.close(self.fd)
9bac43
+
357786
+    def perf_event_open(self, attr, pid, cpu, group_fd, flags):
357786
+        """Wrapper for the sys_perf_evt_open() syscall.
357786
+
357786
+        Used to set up performance events, returns a file descriptor or -1
357786
+        on error.
357786
+
357786
+        Attributes are:
357786
+        - syscall number
357786
+        - struct perf_event_attr *
357786
+        - pid or -1 to monitor all pids
357786
+        - cpu number or -1 to monitor all cpus
357786
+        - The file descriptor of the group leader or -1 to create a group.
357786
+        - flags
357786
+
357786
+        """
357786
+        return self.syscall(ARCH.sc_perf_evt_open, ctypes.pointer(attr),
357786
+                            ctypes.c_int(pid), ctypes.c_int(cpu),
357786
+                            ctypes.c_int(group_fd), ctypes.c_long(flags))
357786
+
9bac43
+    def setup_event_attribute(self, trace_set, trace_point):
9bac43
+        """Returns an initialized ctype perf_event_attr struct."""
9bac43
+
9bac43
+        id_path = os.path.join(PATH_DEBUGFS_TRACING, 'events', trace_set,
9bac43
+                               trace_point, 'id')
9bac43
+
9bac43
+        event_attr = perf_event_attr()
9bac43
+        event_attr.config = int(open(id_path).read())
9bac43
+        return event_attr
9bac43
+
9bac43
+    def setup_event(self, group, trace_cpu, trace_pid, trace_point,
9bac43
+                    trace_filter, trace_set):
9bac43
+        """Sets up the perf event in Linux.
9bac43
+
9bac43
+        Issues the syscall to register the event in the kernel and
9bac43
+        then sets the optional filter.
9bac43
+
9bac43
+        """
9bac43
+
9bac43
+        event_attr = self.setup_event_attribute(trace_set, trace_point)
9bac43
+
9bac43
+        # First event will be group leader.
9bac43
+        group_leader = -1
9bac43
+
9bac43
+        # All others have to pass the leader's descriptor instead.
9bac43
+        if group.events:
9bac43
+            group_leader = group.events[0].fd
9bac43
+
357786
+        fd = self.perf_event_open(event_attr, trace_pid,
357786
+                                  trace_cpu, group_leader, 0)
9bac43
+        if fd == -1:
9bac43
+            err = ctypes.get_errno()
9bac43
+            raise OSError(err, os.strerror(err),
9bac43
+                          'while calling sys_perf_event_open().')
9bac43
+
9bac43
+        if trace_filter:
9bac43
+            fcntl.ioctl(fd, ARCH.ioctl_numbers['SET_FILTER'],
9bac43
+                        trace_filter)
9bac43
+
9bac43
+        self.fd = fd
9bac43
+
9bac43
+    def enable(self):
9bac43
+        """Enables the trace event in the kernel.
9bac43
+
9bac43
+        Enabling the group leader makes reading counters from it and the
9bac43
+        events under it possible.
9bac43
+
9bac43
+        """
9bac43
+        fcntl.ioctl(self.fd, ARCH.ioctl_numbers['ENABLE'], 0)
9bac43
+
9bac43
+    def disable(self):
9bac43
+        """Disables the trace event in the kernel.
9bac43
+
9bac43
+        Disabling the group leader makes reading all counters under it
9bac43
+        impossible.
9bac43
+
9bac43
+        """
9bac43
+        fcntl.ioctl(self.fd, ARCH.ioctl_numbers['DISABLE'], 0)
9bac43
+
9bac43
+    def reset(self):
9bac43
+        """Resets the count of the trace event in the kernel."""
9bac43
+        fcntl.ioctl(self.fd, ARCH.ioctl_numbers['RESET'], 0)
9bac43
+
357786
+
357786
+class Provider(object):
357786
+    """Encapsulates functionalities used by all providers."""
357786
+    @staticmethod
357786
+    def is_field_wanted(fields_filter, field):
357786
+        """Indicate whether field is valid according to fields_filter."""
357786
+        if not fields_filter or fields_filter == "help":
357786
+            return True
357786
+        return re.match(fields_filter, field) is not None
357786
+
357786
+    @staticmethod
357786
+    def walkdir(path):
357786
+        """Returns os.walk() data for specified directory.
357786
+
357786
+        As it is only a wrapper it returns the same 3-tuple of (dirpath,
357786
+        dirnames, filenames).
357786
+        """
357786
+        return next(os.walk(path))
357786
+
357786
+
357786
+class TracepointProvider(Provider):
9bac43
+    """Data provider for the stats class.
9bac43
+
9bac43
+    Manages the events/groups from which it acquires its data.
9bac43
+
9bac43
+    """
357786
+    def __init__(self, pid, fields_filter):
9bac43
+        self.group_leaders = []
357786
+        self.filters = self.get_filters()
357786
+        self.update_fields(fields_filter)
357786
+        self.pid = pid
357786
+
357786
+    @staticmethod
357786
+    def get_filters():
357786
+        """Returns a dict of trace events, their filter ids and
357786
+        the values that can be filtered.
357786
+
357786
+        Trace events can be filtered for special values by setting a
357786
+        filter string via an ioctl. The string normally has the format
357786
+        identifier==value. For each filter a new event will be created, to
357786
+        be able to distinguish the events.
357786
+
357786
+        """
357786
+        filters = {}
357786
+        filters['kvm_userspace_exit'] = ('reason', USERSPACE_EXIT_REASONS)
357786
+        if ARCH.exit_reasons:
357786
+            filters['kvm_exit'] = ('exit_reason', ARCH.exit_reasons)
357786
+        return filters
9bac43
+
9bac43
+    def get_available_fields(self):
9bac43
+        """Returns a list of available event's of format 'event name(filter
9bac43
+        name)'.
9bac43
+
9bac43
+        All available events have directories under
9bac43
+        /sys/kernel/debug/tracing/events/ which export information
9bac43
+        about the specific event. Therefore, listing the dirs gives us
9bac43
+        a list of all available events.
9bac43
+
9bac43
+        Some events like the vm exit reasons can be filtered for
9bac43
+        specific values. To take account for that, the routine below
9bac43
+        creates special fields with the following format:
9bac43
+        event name(filter name)
9bac43
+
9bac43
+        """
9bac43
+        path = os.path.join(PATH_DEBUGFS_TRACING, 'events', 'kvm')
357786
+        fields = self.walkdir(path)[1]
9bac43
+        extra = []
9bac43
+        for field in fields:
9bac43
+            if field in self.filters:
9bac43
+                filter_name_, filter_dicts = self.filters[field]
9bac43
+                for name in filter_dicts:
9bac43
+                    extra.append(field + '(' + name + ')')
9bac43
+        fields += extra
9bac43
+        return fields
9bac43
+
357786
+    def update_fields(self, fields_filter):
357786
+        """Refresh fields, applying fields_filter"""
357786
+        self._fields = [field for field in self.get_available_fields()
357786
+                        if self.is_field_wanted(fields_filter, field)]
357786
+
357786
+    @staticmethod
357786
+    def get_online_cpus():
357786
+        """Returns a list of cpu id integers."""
357786
+        def parse_int_list(list_string):
357786
+            """Returns an int list from a string of comma separated integers and
357786
+            integer ranges."""
357786
+            integers = []
357786
+            members = list_string.split(',')
357786
+
357786
+            for member in members:
357786
+                if '-' not in member:
357786
+                    integers.append(int(member))
357786
+                else:
357786
+                    int_range = member.split('-')
357786
+                    integers.extend(range(int(int_range[0]),
357786
+                                          int(int_range[1]) + 1))
357786
+
357786
+            return integers
357786
+
357786
+        with open('/sys/devices/system/cpu/online') as cpu_list:
357786
+            cpu_string = cpu_list.readline()
357786
+            return parse_int_list(cpu_string)
357786
+
9bac43
+    def setup_traces(self):
9bac43
+        """Creates all event and group objects needed to be able to retrieve
9bac43
+        data."""
357786
+        fields = self.get_available_fields()
9bac43
+        if self._pid > 0:
9bac43
+            # Fetch list of all threads of the monitored pid, as qemu
9bac43
+            # starts a thread for each vcpu.
9bac43
+            path = os.path.join('/proc', str(self._pid), 'task')
357786
+            groupids = self.walkdir(path)[1]
9bac43
+        else:
357786
+            groupids = self.get_online_cpus()
9bac43
+
9bac43
+        # The constant is needed as a buffer for python libs, std
9bac43
+        # streams and other files that the script opens.
357786
+        newlim = len(groupids) * len(fields) + 50
9bac43
+        try:
9bac43
+            softlim_, hardlim = resource.getrlimit(resource.RLIMIT_NOFILE)
9bac43
+
9bac43
+            if hardlim < newlim:
9bac43
+                # Now we need CAP_SYS_RESOURCE, to increase the hard limit.
9bac43
+                resource.setrlimit(resource.RLIMIT_NOFILE, (newlim, newlim))
9bac43
+            else:
9bac43
+                # Raising the soft limit is sufficient.
9bac43
+                resource.setrlimit(resource.RLIMIT_NOFILE, (newlim, hardlim))
9bac43
+
9bac43
+        except ValueError:
9bac43
+            sys.exit("NOFILE rlimit could not be raised to {0}".format(newlim))
9bac43
+
9bac43
+        for groupid in groupids:
9bac43
+            group = Group()
357786
+            for name in fields:
9bac43
+                tracepoint = name
9bac43
+                tracefilter = None
9bac43
+                match = re.match(r'(.*)\((.*)\)', name)
9bac43
+                if match:
9bac43
+                    tracepoint, sub = match.groups()
9bac43
+                    tracefilter = ('%s==%d\0' %
9bac43
+                                   (self.filters[tracepoint][0],
9bac43
+                                    self.filters[tracepoint][1][sub]))
9bac43
+
9bac43
+                # From perf_event_open(2):
9bac43
+                # pid > 0 and cpu == -1
9bac43
+                # This measures the specified process/thread on any CPU.
9bac43
+                #
9bac43
+                # pid == -1 and cpu >= 0
9bac43
+                # This measures all processes/threads on the specified CPU.
9bac43
+                trace_cpu = groupid if self._pid == 0 else -1
9bac43
+                trace_pid = int(groupid) if self._pid != 0 else -1
9bac43
+
9bac43
+                group.add_event(Event(name=name,
9bac43
+                                      group=group,
9bac43
+                                      trace_cpu=trace_cpu,
9bac43
+                                      trace_pid=trace_pid,
9bac43
+                                      trace_point=tracepoint,
9bac43
+                                      trace_filter=tracefilter))
9bac43
+
9bac43
+            self.group_leaders.append(group)
9bac43
+
9bac43
+    @property
9bac43
+    def fields(self):
9bac43
+        return self._fields
9bac43
+
9bac43
+    @fields.setter
9bac43
+    def fields(self, fields):
9bac43
+        """Enables/disables the (un)wanted events"""
9bac43
+        self._fields = fields
9bac43
+        for group in self.group_leaders:
9bac43
+            for index, event in enumerate(group.events):
9bac43
+                if event.name in fields:
9bac43
+                    event.reset()
9bac43
+                    event.enable()
9bac43
+                else:
9bac43
+                    # Do not disable the group leader.
9bac43
+                    # It would disable all of its events.
9bac43
+                    if index != 0:
9bac43
+                        event.disable()
9bac43
+
9bac43
+    @property
9bac43
+    def pid(self):
9bac43
+        return self._pid
9bac43
+
9bac43
+    @pid.setter
9bac43
+    def pid(self, pid):
9bac43
+        """Changes the monitored pid by setting new traces."""
9bac43
+        self._pid = pid
9bac43
+        # The garbage collector will get rid of all Event/Group
9bac43
+        # objects and open files after removing the references.
9bac43
+        self.group_leaders = []
9bac43
+        self.setup_traces()
9bac43
+        self.fields = self._fields
9bac43
+
357786
+    def read(self, by_guest=0):
9bac43
+        """Returns 'event name: current value' for all enabled events."""
9bac43
+        ret = defaultdict(int)
9bac43
+        for group in self.group_leaders:
9bac43
+            for name, val in group.read().iteritems():
9bac43
+                if name in self._fields:
9bac43
+                    ret[name] += val
9bac43
+        return ret
9bac43
+
357786
+    def reset(self):
357786
+        """Reset all field counters"""
357786
+        for group in self.group_leaders:
357786
+            for event in group.events:
357786
+                event.reset()
357786
+
357786
+
357786
+class DebugfsProvider(Provider):
9bac43
+    """Provides data from the files that KVM creates in the kvm debugfs
9bac43
+    folder."""
357786
+    def __init__(self, pid, fields_filter, include_past):
357786
+        self.update_fields(fields_filter)
357786
+        self._baseline = {}
9bac43
+        self.do_read = True
357786
+        self.paths = []
357786
+        self.pid = pid
357786
+        if include_past:
357786
+            self.restore()
9bac43
+
9bac43
+    def get_available_fields(self):
9bac43
+        """"Returns a list of available fields.
9bac43
+
9bac43
+        The fields are all available KVM debugfs files
9bac43
+
9bac43
+        """
357786
+        return self.walkdir(PATH_DEBUGFS_KVM)[2]
357786
+
357786
+    def update_fields(self, fields_filter):
357786
+        """Refresh fields, applying fields_filter"""
357786
+        self._fields = [field for field in self.get_available_fields()
357786
+                        if self.is_field_wanted(fields_filter, field)]
9bac43
+
9bac43
+    @property
9bac43
+    def fields(self):
9bac43
+        return self._fields
9bac43
+
9bac43
+    @fields.setter
9bac43
+    def fields(self, fields):
9bac43
+        self._fields = fields
357786
+        self.reset()
9bac43
+
9bac43
+    @property
9bac43
+    def pid(self):
9bac43
+        return self._pid
9bac43
+
9bac43
+    @pid.setter
9bac43
+    def pid(self, pid):
357786
+        self._pid = pid
9bac43
+        if pid != 0:
357786
+            vms = self.walkdir(PATH_DEBUGFS_KVM)[1]
9bac43
+            if len(vms) == 0:
9bac43
+                self.do_read = False
9bac43
+
9bac43
+            self.paths = filter(lambda x: "{}-".format(pid) in x, vms)
9bac43
+
9bac43
+        else:
357786
+            self.paths = []
9bac43
+            self.do_read = True
357786
+        self.reset()
9bac43
+
357786
+    def read(self, reset=0, by_guest=0):
357786
+        """Returns a dict with format:'file name / field -> current value'.
357786
+
357786
+        Parameter 'reset':
357786
+          0   plain read
357786
+          1   reset field counts to 0
357786
+          2   restore the original field counts
357786
+
357786
+        """
9bac43
+        results = {}
9bac43
+
9bac43
+        # If no debugfs filtering support is available, then don't read.
9bac43
+        if not self.do_read:
9bac43
+            return results
9bac43
+
357786
+        paths = self.paths
357786
+        if self._pid == 0:
357786
+            paths = []
357786
+            for entry in os.walk(PATH_DEBUGFS_KVM):
357786
+                for dir in entry[1]:
357786
+                    paths.append(dir)
357786
+        for path in paths:
9bac43
+            for field in self._fields:
357786
+                value = self.read_field(field, path)
357786
+                key = path + field
357786
+                if reset == 1:
357786
+                    self._baseline[key] = value
357786
+                if reset == 2:
357786
+                    self._baseline[key] = 0
357786
+                if self._baseline.get(key, -1) == -1:
357786
+                    self._baseline[key] = value
357786
+                increment = (results.get(field, 0) + value -
357786
+                             self._baseline.get(key, 0))
357786
+                if by_guest:
357786
+                    pid = key.split('-')[0]
357786
+                    if pid in results:
357786
+                        results[pid] += increment
357786
+                    else:
357786
+                        results[pid] = increment
357786
+                else:
357786
+                    results[field] = increment
9bac43
+
9bac43
+        return results
9bac43
+
9bac43
+    def read_field(self, field, path):
9bac43
+        """Returns the value of a single field from a specific VM."""
9bac43
+        try:
9bac43
+            return int(open(os.path.join(PATH_DEBUGFS_KVM,
9bac43
+                                         path,
9bac43
+                                         field))
9bac43
+                       .read())
9bac43
+        except IOError:
9bac43
+            return 0
9bac43
+
357786
+    def reset(self):
357786
+        """Reset field counters"""
357786
+        self._baseline = {}
357786
+        self.read(1)
357786
+
357786
+    def restore(self):
357786
+        """Reset field counters"""
357786
+        self._baseline = {}
357786
+        self.read(2)
357786
+
357786
+
9bac43
+class Stats(object):
9bac43
+    """Manages the data providers and the data they provide.
9bac43
+
9bac43
+    It is used to set filters on the provider's data and collect all
9bac43
+    provider data.
9bac43
+
9bac43
+    """
357786
+    def __init__(self, options):
357786
+        self.providers = self.get_providers(options)
357786
+        self._pid_filter = options.pid
357786
+        self._fields_filter = options.fields
9bac43
+        self.values = {}
357786
+
357786
+    @staticmethod
357786
+    def get_providers(options):
357786
+        """Returns a list of data providers depending on the passed options."""
357786
+        providers = []
357786
+
357786
+        if options.debugfs:
357786
+            providers.append(DebugfsProvider(options.pid, options.fields,
357786
+                                             options.dbgfs_include_past))
357786
+        if options.tracepoints or not providers:
357786
+            providers.append(TracepointProvider(options.pid, options.fields))
357786
+
357786
+        return providers
9bac43
+
9bac43
+    def update_provider_filters(self):
9bac43
+        """Propagates fields filters to providers."""
9bac43
+        # As we reset the counters when updating the fields we can
9bac43
+        # also clear the cache of old values.
9bac43
+        self.values = {}
9bac43
+        for provider in self.providers:
357786
+            provider.update_fields(self._fields_filter)
9bac43
+
357786
+    def reset(self):
357786
+        self.values = {}
9bac43
+        for provider in self.providers:
357786
+            provider.reset()
9bac43
+
9bac43
+    @property
9bac43
+    def fields_filter(self):
9bac43
+        return self._fields_filter
9bac43
+
9bac43
+    @fields_filter.setter
9bac43
+    def fields_filter(self, fields_filter):
357786
+        if fields_filter != self._fields_filter:
357786
+            self._fields_filter = fields_filter
357786
+            self.update_provider_filters()
9bac43
+
9bac43
+    @property
9bac43
+    def pid_filter(self):
9bac43
+        return self._pid_filter
9bac43
+
9bac43
+    @pid_filter.setter
9bac43
+    def pid_filter(self, pid):
357786
+        if pid != self._pid_filter:
357786
+            self._pid_filter = pid
357786
+            self.values = {}
357786
+            for provider in self.providers:
357786
+                provider.pid = self._pid_filter
9bac43
+
357786
+    def get(self, by_guest=0):
9bac43
+        """Returns a dict with field -> (value, delta to last value) of all
9bac43
+        provider data."""
9bac43
+        for provider in self.providers:
357786
+            new = provider.read(by_guest=by_guest)
357786
+            for key in new if by_guest else provider.fields:
357786
+                oldval = self.values.get(key, (0, 0))[0]
9bac43
+                newval = new.get(key, 0)
357786
+                newdelta = newval - oldval
9bac43
+                self.values[key] = (newval, newdelta)
9bac43
+        return self.values
9bac43
+
357786
+    def toggle_display_guests(self, to_pid):
357786
+        """Toggle between collection of stats by individual event and by
357786
+        guest pid
357786
+
357786
+        Events reported by DebugfsProvider change when switching to/from
357786
+        reading by guest values. Hence we have to remove the excess event
357786
+        names from self.values.
357786
+
357786
+        """
357786
+        if any(isinstance(ins, TracepointProvider) for ins in self.providers):
357786
+            return 1
357786
+        if to_pid:
357786
+            for provider in self.providers:
357786
+                if isinstance(provider, DebugfsProvider):
357786
+                    for key in provider.fields:
357786
+                        if key in self.values.keys():
357786
+                            del self.values[key]
357786
+        else:
357786
+            oldvals = self.values.copy()
357786
+            for key in oldvals:
357786
+                if key.isdigit():
357786
+                    del self.values[key]
357786
+        # Update oldval (see get())
357786
+        self.get(to_pid)
357786
+        return 0
357786
+
357786
+DELAY_DEFAULT = 3.0
357786
+MAX_GUEST_NAME_LEN = 48
357786
+MAX_REGEX_LEN = 44
357786
+DEFAULT_REGEX = r'^[^\(]*$'
357786
+SORT_DEFAULT = 0
357786
+
9bac43
+
9bac43
+class Tui(object):
9bac43
+    """Instruments curses to draw a nice text ui."""
9bac43
+    def __init__(self, stats):
9bac43
+        self.stats = stats
9bac43
+        self.screen = None
357786
+        self._delay_initial = 0.25
357786
+        self._delay_regular = DELAY_DEFAULT
357786
+        self._sorting = SORT_DEFAULT
357786
+        self._display_guests = 0
9bac43
+
9bac43
+    def __enter__(self):
9bac43
+        """Initialises curses for later use.  Based on curses.wrapper
9bac43
+           implementation from the Python standard library."""
9bac43
+        self.screen = curses.initscr()
9bac43
+        curses.noecho()
9bac43
+        curses.cbreak()
9bac43
+
9bac43
+        # The try/catch works around a minor bit of
9bac43
+        # over-conscientiousness in the curses module, the error
9bac43
+        # return from C start_color() is ignorable.
9bac43
+        try:
9bac43
+            curses.start_color()
357786
+        except curses.error:
357786
+            pass
357786
+
357786
+        # Hide cursor in extra statement as some monochrome terminals
357786
+        # might support hiding but not colors.
357786
+        try:
357786
+            curses.curs_set(0)
357786
+        except curses.error:
9bac43
+            pass
9bac43
+
9bac43
+        curses.use_default_colors()
9bac43
+        return self
9bac43
+
9bac43
+    def __exit__(self, *exception):
357786
+        """Resets the terminal to its normal state.  Based on curses.wrapper
9bac43
+           implementation from the Python standard library."""
9bac43
+        if self.screen:
9bac43
+            self.screen.keypad(0)
9bac43
+            curses.echo()
9bac43
+            curses.nocbreak()
9bac43
+            curses.endwin()
9bac43
+
357786
+    @staticmethod
357786
+    def get_all_gnames():
357786
+        """Returns a list of (pid, gname) tuples of all running guests"""
357786
+        res = []
357786
+        try:
357786
+            child = subprocess.Popen(['ps', '-A', '--format', 'pid,args'],
357786
+                                     stdout=subprocess.PIPE)
357786
+        except:
357786
+            raise Exception
357786
+        for line in child.stdout:
357786
+            line = line.lstrip().split(' ', 1)
357786
+            # perform a sanity check before calling the more expensive
357786
+            # function to possibly extract the guest name
357786
+            if ' -name ' in line[1]:
357786
+                res.append((line[0], Tui.get_gname_from_pid(line[0])))
357786
+        child.stdout.close()
357786
+
357786
+        return res
357786
+
357786
+    def print_all_gnames(self, row):
357786
+        """Print a list of all running guests along with their pids."""
357786
+        self.screen.addstr(row, 2, '%8s  %-60s' %
357786
+                           ('Pid', 'Guest Name (fuzzy list, might be '
357786
+                            'inaccurate!)'),
357786
+                           curses.A_UNDERLINE)
357786
+        row += 1
357786
+        try:
357786
+            for line in self.get_all_gnames():
357786
+                self.screen.addstr(row, 2, '%8s  %-60s' % (line[0], line[1]))
357786
+                row += 1
357786
+                if row >= self.screen.getmaxyx()[0]:
357786
+                    break
357786
+        except Exception:
357786
+            self.screen.addstr(row + 1, 2, 'Not available')
357786
+
357786
+    @staticmethod
357786
+    def get_pid_from_gname(gname):
357786
+        """Fuzzy function to convert guest name to QEMU process pid.
357786
+
357786
+        Returns a list of potential pids, can be empty if no match found.
357786
+        Throws an exception on processing errors.
357786
+
357786
+        """
357786
+        pids = []
357786
+        for line in Tui.get_all_gnames():
357786
+            if gname == line[1]:
357786
+                pids.append(int(line[0]))
357786
+
357786
+        return pids
357786
+
357786
+    @staticmethod
357786
+    def get_gname_from_pid(pid):
357786
+        """Returns the guest name for a QEMU process pid.
357786
+
357786
+        Extracts the guest name from the QEMU comma line by processing the
357786
+        '-name' option. Will also handle names specified out of sequence.
357786
+
357786
+        """
357786
+        name = ''
357786
+        try:
357786
+            line = open('/proc/{}/cmdline'
357786
+                        .format(pid), 'rb').read().split('\0')
357786
+            parms = line[line.index('-name') + 1].split(',')
357786
+            while '' in parms:
357786
+                # commas are escaped (i.e. ',,'), hence e.g. 'foo,bar' results
357786
+                # in # ['foo', '', 'bar'], which we revert here
357786
+                idx = parms.index('')
357786
+                parms[idx - 1] += ',' + parms[idx + 1]
357786
+                del parms[idx:idx+2]
357786
+            # the '-name' switch allows for two ways to specify the guest name,
357786
+            # where the plain name overrides the name specified via 'guest='
357786
+            for arg in parms:
357786
+                if '=' not in arg:
357786
+                    name = arg
357786
+                    break
357786
+                if arg[:6] == 'guest=':
357786
+                    name = arg[6:]
357786
+        except (ValueError, IOError, IndexError):
357786
+            pass
357786
+
357786
+        return name
357786
+
9bac43
+    def update_drilldown(self):
9bac43
+        """Sets or removes a filter that only allows fields without braces."""
9bac43
+        if not self.stats.fields_filter:
357786
+            self.stats.fields_filter = DEFAULT_REGEX
9bac43
+
357786
+        elif self.stats.fields_filter == DEFAULT_REGEX:
9bac43
+            self.stats.fields_filter = None
9bac43
+
9bac43
+    def update_pid(self, pid):
9bac43
+        """Propagates pid selection to stats object."""
9bac43
+        self.stats.pid_filter = pid
9bac43
+
357786
+    def refresh_header(self, pid=None):
357786
+        """Refreshes the header."""
357786
+        if pid is None:
357786
+            pid = self.stats.pid_filter
9bac43
+        self.screen.erase()
357786
+        gname = self.get_gname_from_pid(pid)
357786
+        if gname:
357786
+            gname = ('({})'.format(gname[:MAX_GUEST_NAME_LEN] + '...'
357786
+                                   if len(gname) > MAX_GUEST_NAME_LEN
357786
+                                   else gname))
357786
+        if pid > 0:
357786
+            self.screen.addstr(0, 0, 'kvm statistics - pid {0} {1}'
357786
+                               .format(pid, gname), curses.A_BOLD)
9bac43
+        else:
9bac43
+            self.screen.addstr(0, 0, 'kvm statistics - summary', curses.A_BOLD)
357786
+        if self.stats.fields_filter and self.stats.fields_filter \
357786
+           != DEFAULT_REGEX:
357786
+            regex = self.stats.fields_filter
357786
+            if len(regex) > MAX_REGEX_LEN:
357786
+                regex = regex[:MAX_REGEX_LEN] + '...'
357786
+            self.screen.addstr(1, 17, 'regex filter: {0}'.format(regex))
357786
+        if self._display_guests:
357786
+            col_name = 'Guest Name'
357786
+        else:
357786
+            col_name = 'Event'
357786
+        self.screen.addstr(2, 1, '%-40s %10s%7s %8s' %
357786
+                           (col_name, 'Total', '%Total', 'CurAvg/s'),
357786
+                           curses.A_STANDOUT)
357786
+        self.screen.addstr(4, 1, 'Collecting data...')
357786
+        self.screen.refresh()
357786
+
357786
+    def refresh_body(self, sleeptime):
9bac43
+        row = 3
357786
+        self.screen.move(row, 0)
357786
+        self.screen.clrtobot()
357786
+        stats = self.stats.get(self._display_guests)
357786
+
357786
+        def sortCurAvg(x):
357786
+            # sort by current events if available
9bac43
+            if stats[x][1]:
9bac43
+                return (-stats[x][1], -stats[x][0])
9bac43
+            else:
9bac43
+                return (0, -stats[x][0])
357786
+
357786
+        def sortTotal(x):
357786
+            # sort by totals
357786
+            return (0, -stats[x][0])
357786
+        total = 0.
357786
+        for val in stats.values():
357786
+            total += val[0]
357786
+        if self._sorting == SORT_DEFAULT:
357786
+            sortkey = sortCurAvg
357786
+        else:
357786
+            sortkey = sortTotal
9bac43
+        for key in sorted(stats.keys(), key=sortkey):
9bac43
+
9bac43
+            if row >= self.screen.getmaxyx()[0]:
9bac43
+                break
9bac43
+            values = stats[key]
9bac43
+            if not values[0] and not values[1]:
9bac43
+                break
357786
+            if values[0] is not None:
357786
+                cur = int(round(values[1] / sleeptime)) if values[1] else ''
357786
+                if self._display_guests:
357786
+                    key = self.get_gname_from_pid(key)
357786
+                self.screen.addstr(row, 1, '%-40s %10d%7.1f %8s' %
357786
+                                   (key, values[0], values[0] * 100 / total,
357786
+                                    cur))
9bac43
+            row += 1
357786
+        if row == 3:
357786
+            self.screen.addstr(4, 1, 'No matching events reported yet')
9bac43
+        self.screen.refresh()
9bac43
+
357786
+    def show_msg(self, text):
357786
+        """Display message centered text and exit on key press"""
357786
+        hint = 'Press any key to continue'
357786
+        curses.cbreak()
357786
+        self.screen.erase()
357786
+        (x, term_width) = self.screen.getmaxyx()
357786
+        row = 2
357786
+        for line in text:
357786
+            start = (term_width - len(line)) / 2
357786
+            self.screen.addstr(row, start, line)
357786
+            row += 1
357786
+        self.screen.addstr(row + 1, (term_width - len(hint)) / 2, hint,
357786
+                           curses.A_STANDOUT)
357786
+        self.screen.getkey()
357786
+
357786
+    def show_help_interactive(self):
357786
+        """Display help with list of interactive commands"""
357786
+        msg = ('   b     toggle events by guests (debugfs only, honors'
357786
+               ' filters)',
357786
+               '   c     clear filter',
357786
+               '   f     filter by regular expression',
357786
+               '   g     filter by guest name',
357786
+               '   h     display interactive commands reference',
357786
+               '   o     toggle sorting order (Total vs CurAvg/s)',
357786
+               '   p     filter by PID',
357786
+               '   q     quit',
357786
+               '   r     reset stats',
357786
+               '   s     set update interval',
357786
+               '   x     toggle reporting of stats for individual child trace'
357786
+               ' events',
357786
+               'Any other key refreshes statistics immediately')
357786
+        curses.cbreak()
357786
+        self.screen.erase()
357786
+        self.screen.addstr(0, 0, "Interactive commands reference",
357786
+                           curses.A_BOLD)
357786
+        self.screen.addstr(2, 0, "Press any key to exit", curses.A_STANDOUT)
357786
+        row = 4
357786
+        for line in msg:
357786
+            self.screen.addstr(row, 0, line)
357786
+            row += 1
357786
+        self.screen.getkey()
357786
+        self.refresh_header()
357786
+
9bac43
+    def show_filter_selection(self):
9bac43
+        """Draws filter selection mask.
9bac43
+
9bac43
+        Asks for a valid regex and sets the fields filter accordingly.
9bac43
+
9bac43
+        """
9bac43
+        while True:
9bac43
+            self.screen.erase()
9bac43
+            self.screen.addstr(0, 0,
9bac43
+                               "Show statistics for events matching a regex.",
9bac43
+                               curses.A_BOLD)
9bac43
+            self.screen.addstr(2, 0,
9bac43
+                               "Current regex: {0}"
9bac43
+                               .format(self.stats.fields_filter))
9bac43
+            self.screen.addstr(3, 0, "New regex: ")
9bac43
+            curses.echo()
9bac43
+            regex = self.screen.getstr()
9bac43
+            curses.noecho()
9bac43
+            if len(regex) == 0:
357786
+                self.stats.fields_filter = DEFAULT_REGEX
357786
+                self.refresh_header()
9bac43
+                return
9bac43
+            try:
9bac43
+                re.compile(regex)
9bac43
+                self.stats.fields_filter = regex
357786
+                self.refresh_header()
9bac43
+                return
9bac43
+            except re.error:
9bac43
+                continue
9bac43
+
357786
+    def show_vm_selection_by_pid(self):
9bac43
+        """Draws PID selection mask.
9bac43
+
9bac43
+        Asks for a pid until a valid pid or 0 has been entered.
9bac43
+
9bac43
+        """
357786
+        msg = ''
9bac43
+        while True:
9bac43
+            self.screen.erase()
9bac43
+            self.screen.addstr(0, 0,
9bac43
+                               'Show statistics for specific pid.',
9bac43
+                               curses.A_BOLD)
9bac43
+            self.screen.addstr(1, 0,
9bac43
+                               'This might limit the shown data to the trace '
9bac43
+                               'statistics.')
357786
+            self.screen.addstr(5, 0, msg)
357786
+            self.print_all_gnames(7)
9bac43
+
9bac43
+            curses.echo()
9bac43
+            self.screen.addstr(3, 0, "Pid [0 or pid]: ")
9bac43
+            pid = self.screen.getstr()
9bac43
+            curses.noecho()
9bac43
+
9bac43
+            try:
357786
+                if len(pid) > 0:
357786
+                    pid = int(pid)
357786
+                    if pid != 0 and not os.path.isdir(os.path.join('/proc/',
357786
+                                                                   str(pid))):
357786
+                        msg = '"' + str(pid) + '": Not a running process'
357786
+                        continue
9bac43
+                else:
357786
+                    pid = 0
357786
+                self.refresh_header(pid)
357786
+                self.update_pid(pid)
357786
+                break
357786
+            except ValueError:
357786
+                msg = '"' + str(pid) + '": Not a valid pid'
357786
+
357786
+    def show_set_update_interval(self):
357786
+        """Draws update interval selection mask."""
357786
+        msg = ''
357786
+        while True:
357786
+            self.screen.erase()
357786
+            self.screen.addstr(0, 0, 'Set update interval (defaults to %fs).' %
357786
+                               DELAY_DEFAULT, curses.A_BOLD)
357786
+            self.screen.addstr(4, 0, msg)
357786
+            self.screen.addstr(2, 0, 'Change delay from %.1fs to ' %
357786
+                               self._delay_regular)
357786
+            curses.echo()
357786
+            val = self.screen.getstr()
357786
+            curses.noecho()
357786
+
357786
+            try:
357786
+                if len(val) > 0:
357786
+                    delay = float(val)
357786
+                    if delay < 0.1:
357786
+                        msg = '"' + str(val) + '": Value must be >=0.1'
9bac43
+                        continue
357786
+                    if delay > 25.5:
357786
+                        msg = '"' + str(val) + '": Value must be <=25.5'
357786
+                        continue
357786
+                else:
357786
+                    delay = DELAY_DEFAULT
357786
+                self._delay_regular = delay
357786
+                break
9bac43
+
9bac43
+            except ValueError:
357786
+                msg = '"' + str(val) + '": Invalid value'
357786
+        self.refresh_header()
357786
+
357786
+    def show_vm_selection_by_guest_name(self):
357786
+        """Draws guest selection mask.
357786
+
357786
+        Asks for a guest name until a valid guest name or '' is entered.
357786
+
357786
+        """
357786
+        msg = ''
357786
+        while True:
357786
+            self.screen.erase()
357786
+            self.screen.addstr(0, 0,
357786
+                               'Show statistics for specific guest.',
357786
+                               curses.A_BOLD)
357786
+            self.screen.addstr(1, 0,
357786
+                               'This might limit the shown data to the trace '
357786
+                               'statistics.')
357786
+            self.screen.addstr(5, 0, msg)
357786
+            self.print_all_gnames(7)
357786
+            curses.echo()
357786
+            self.screen.addstr(3, 0, "Guest [ENTER or guest]: ")
357786
+            gname = self.screen.getstr()
357786
+            curses.noecho()
357786
+
357786
+            if not gname:
357786
+                self.refresh_header(0)
357786
+                self.update_pid(0)
357786
+                break
357786
+            else:
357786
+                pids = []
357786
+                try:
357786
+                    pids = self.get_pid_from_gname(gname)
357786
+                except:
357786
+                    msg = '"' + gname + '": Internal error while searching, ' \
357786
+                          'use pid filter instead'
357786
+                    continue
357786
+                if len(pids) == 0:
357786
+                    msg = '"' + gname + '": Not an active guest'
357786
+                    continue
357786
+                if len(pids) > 1:
357786
+                    msg = '"' + gname + '": Multiple matches found, use pid ' \
357786
+                          'filter instead'
357786
+                    continue
357786
+                self.refresh_header(pids[0])
357786
+                self.update_pid(pids[0])
357786
+                break
9bac43
+
9bac43
+    def show_stats(self):
9bac43
+        """Refreshes the screen and processes user input."""
357786
+        sleeptime = self._delay_initial
357786
+        self.refresh_header()
357786
+        start = 0.0  # result based on init value never appears on screen
9bac43
+        while True:
357786
+            self.refresh_body(time.time() - start)
9bac43
+            curses.halfdelay(int(sleeptime * 10))
357786
+            start = time.time()
357786
+            sleeptime = self._delay_regular
9bac43
+            try:
9bac43
+                char = self.screen.getkey()
357786
+                if char == 'b':
357786
+                    self._display_guests = not self._display_guests
357786
+                    if self.stats.toggle_display_guests(self._display_guests):
357786
+                        self.show_msg(['Command not available with tracepoints'
357786
+                                       ' enabled', 'Restart with debugfs only '
357786
+                                       '(see option \'-d\') and try again!'])
357786
+                        self._display_guests = not self._display_guests
357786
+                    self.refresh_header()
357786
+                if char == 'c':
357786
+                    self.stats.fields_filter = DEFAULT_REGEX
357786
+                    self.refresh_header(0)
357786
+                    self.update_pid(0)
9bac43
+                if char == 'f':
357786
+                    curses.curs_set(1)
9bac43
+                    self.show_filter_selection()
357786
+                    curses.curs_set(0)
357786
+                    sleeptime = self._delay_initial
357786
+                if char == 'g':
357786
+                    curses.curs_set(1)
357786
+                    self.show_vm_selection_by_guest_name()
357786
+                    curses.curs_set(0)
357786
+                    sleeptime = self._delay_initial
357786
+                if char == 'h':
357786
+                    self.show_help_interactive()
357786
+                if char == 'o':
357786
+                    self._sorting = not self._sorting
9bac43
+                if char == 'p':
357786
+                    curses.curs_set(1)
357786
+                    self.show_vm_selection_by_pid()
357786
+                    curses.curs_set(0)
357786
+                    sleeptime = self._delay_initial
357786
+                if char == 'q':
357786
+                    break
357786
+                if char == 'r':
357786
+                    self.stats.reset()
357786
+                if char == 's':
357786
+                    curses.curs_set(1)
357786
+                    self.show_set_update_interval()
357786
+                    curses.curs_set(0)
357786
+                    sleeptime = self._delay_initial
357786
+                if char == 'x':
357786
+                    self.update_drilldown()
357786
+                    # prevents display of current values on next refresh
357786
+                    self.stats.get()
9bac43
+            except KeyboardInterrupt:
9bac43
+                break
9bac43
+            except curses.error:
9bac43
+                continue
9bac43
+
357786
+
9bac43
+def batch(stats):
9bac43
+    """Prints statistics in a key, value format."""
357786
+    try:
357786
+        s = stats.get()
357786
+        time.sleep(1)
357786
+        s = stats.get()
357786
+        for key in sorted(s.keys()):
357786
+            values = s[key]
357786
+            print '%-42s%10d%10d' % (key, values[0], values[1])
357786
+    except KeyboardInterrupt:
357786
+        pass
357786
+
9bac43
+
9bac43
+def log(stats):
9bac43
+    """Prints statistics as reiterating key block, multiple value blocks."""
9bac43
+    keys = sorted(stats.get().iterkeys())
357786
+
9bac43
+    def banner():
9bac43
+        for k in keys:
9bac43
+            print '%s' % k,
9bac43
+        print
357786
+
9bac43
+    def statline():
9bac43
+        s = stats.get()
9bac43
+        for k in keys:
9bac43
+            print ' %9d' % s[k][1],
9bac43
+        print
9bac43
+    line = 0
9bac43
+    banner_repeat = 20
9bac43
+    while True:
357786
+        try:
357786
+            time.sleep(1)
357786
+            if line % banner_repeat == 0:
357786
+                banner()
357786
+            statline()
357786
+            line += 1
357786
+        except KeyboardInterrupt:
357786
+            break
357786
+
9bac43
+
9bac43
+def get_options():
9bac43
+    """Returns processed program arguments."""
9bac43
+    description_text = """
9bac43
+This script displays various statistics about VMs running under KVM.
9bac43
+The statistics are gathered from the KVM debugfs entries and / or the
9bac43
+currently available perf traces.
9bac43
+
9bac43
+The monitoring takes additional cpu cycles and might affect the VM's
9bac43
+performance.
9bac43
+
9bac43
+Requirements:
9bac43
+- Access to:
357786
+    %s
357786
+    %s/events/*
9bac43
+    /proc/pid/task
9bac43
+- /proc/sys/kernel/perf_event_paranoid < 1 if user has no
9bac43
+  CAP_SYS_ADMIN and perf events are used.
9bac43
+- CAP_SYS_RESOURCE if the hard limit is not high enough to allow
9bac43
+  the large number of files that are possibly opened.
357786
+
357786
+Interactive Commands:
357786
+   b     toggle events by guests (debugfs only, honors filters)
357786
+   c     clear filter
357786
+   f     filter by regular expression
357786
+   g     filter by guest name
357786
+   h     display interactive commands reference
357786
+   o     toggle sorting order (Total vs CurAvg/s)
357786
+   p     filter by PID
357786
+   q     quit
357786
+   r     reset stats
357786
+   s     set update interval
357786
+   x     toggle reporting of stats for individual child trace events
357786
+Press any other key to refresh statistics immediately.
357786
+""" % (PATH_DEBUGFS_KVM, PATH_DEBUGFS_TRACING)
9bac43
+
9bac43
+    class PlainHelpFormatter(optparse.IndentedHelpFormatter):
9bac43
+        def format_description(self, description):
9bac43
+            if description:
9bac43
+                return description + "\n"
9bac43
+            else:
9bac43
+                return ""
9bac43
+
357786
+    def cb_guest_to_pid(option, opt, val, parser):
357786
+        try:
357786
+            pids = Tui.get_pid_from_gname(val)
357786
+        except:
357786
+            raise optparse.OptionValueError('Error while searching for guest '
357786
+                                            '"{}", use "-p" to specify a pid '
357786
+                                            'instead'.format(val))
357786
+        if len(pids) == 0:
357786
+            raise optparse.OptionValueError('No guest by the name "{}" '
357786
+                                            'found'.format(val))
357786
+        if len(pids) > 1:
357786
+            raise optparse.OptionValueError('Multiple processes found (pids: '
357786
+                                            '{}) - use "-p" to specify a pid '
357786
+                                            'instead'.format(" ".join(pids)))
357786
+        parser.values.pid = pids[0]
357786
+
9bac43
+    optparser = optparse.OptionParser(description=description_text,
9bac43
+                                      formatter=PlainHelpFormatter())
9bac43
+    optparser.add_option('-1', '--once', '--batch',
9bac43
+                         action='store_true',
9bac43
+                         default=False,
9bac43
+                         dest='once',
9bac43
+                         help='run in batch mode for one second',
9bac43
+                         )
357786
+    optparser.add_option('-i', '--debugfs-include-past',
357786
+                         action='store_true',
357786
+                         default=False,
357786
+                         dest='dbgfs_include_past',
357786
+                         help='include all available data on past events for '
357786
+                              'debugfs',
357786
+                         )
9bac43
+    optparser.add_option('-l', '--log',
9bac43
+                         action='store_true',
9bac43
+                         default=False,
9bac43
+                         dest='log',
9bac43
+                         help='run in logging mode (like vmstat)',
9bac43
+                         )
9bac43
+    optparser.add_option('-t', '--tracepoints',
9bac43
+                         action='store_true',
9bac43
+                         default=False,
9bac43
+                         dest='tracepoints',
9bac43
+                         help='retrieve statistics from tracepoints',
9bac43
+                         )
9bac43
+    optparser.add_option('-d', '--debugfs',
9bac43
+                         action='store_true',
9bac43
+                         default=False,
9bac43
+                         dest='debugfs',
9bac43
+                         help='retrieve statistics from debugfs',
9bac43
+                         )
9bac43
+    optparser.add_option('-f', '--fields',
9bac43
+                         action='store',
357786
+                         default=DEFAULT_REGEX,
9bac43
+                         dest='fields',
357786
+                         help='''fields to display (regex)
357786
+                                 "-f help" for a list of available events''',
9bac43
+                         )
9bac43
+    optparser.add_option('-p', '--pid',
357786
+                         action='store',
357786
+                         default=0,
357786
+                         type='int',
357786
+                         dest='pid',
357786
+                         help='restrict statistics to pid',
357786
+                         )
357786
+    optparser.add_option('-g', '--guest',
357786
+                         action='callback',
357786
+                         type='string',
357786
+                         dest='pid',
357786
+                         metavar='GUEST',
357786
+                         help='restrict statistics to guest by name',
357786
+                         callback=cb_guest_to_pid,
357786
+                         )
9bac43
+    (options, _) = optparser.parse_args(sys.argv)
9bac43
+    return options
9bac43
+
9bac43
+
9bac43
+def check_access(options):
9bac43
+    """Exits if the current user can't access all needed directories."""
9bac43
+    if not os.path.exists('/sys/kernel/debug'):
9bac43
+        sys.stderr.write('Please enable CONFIG_DEBUG_FS in your kernel.')
9bac43
+        sys.exit(1)
9bac43
+
9bac43
+    if not os.path.exists(PATH_DEBUGFS_KVM):
9bac43
+        sys.stderr.write("Please make sure, that debugfs is mounted and "
9bac43
+                         "readable by the current user:\n"
9bac43
+                         "('mount -t debugfs debugfs /sys/kernel/debug')\n"
9bac43
+                         "Also ensure, that the kvm modules are loaded.\n")
9bac43
+        sys.exit(1)
9bac43
+
357786
+    if not os.path.exists(PATH_DEBUGFS_TRACING) and (options.tracepoints or
357786
+                                                     not options.debugfs):
9bac43
+        sys.stderr.write("Please enable CONFIG_TRACING in your kernel "
9bac43
+                         "when using the option -t (default).\n"
9bac43
+                         "If it is enabled, make {0} readable by the "
9bac43
+                         "current user.\n"
9bac43
+                         .format(PATH_DEBUGFS_TRACING))
9bac43
+        if options.tracepoints:
9bac43
+            sys.exit(1)
9bac43
+
9bac43
+        sys.stderr.write("Falling back to debugfs statistics!\n")
9bac43
+        options.debugfs = True
357786
+        time.sleep(5)
9bac43
+
9bac43
+    return options
9bac43
+
357786
+
9bac43
+def main():
9bac43
+    options = get_options()
9bac43
+    options = check_access(options)
9bac43
+
9bac43
+    if (options.pid > 0 and
9bac43
+        not os.path.isdir(os.path.join('/proc/',
9bac43
+                                       str(options.pid)))):
9bac43
+        sys.stderr.write('Did you use a (unsupported) tid instead of a pid?\n')
9bac43
+        sys.exit('Specified pid does not exist.')
9bac43
+
357786
+    stats = Stats(options)
357786
+
357786
+    if options.fields == "help":
357786
+        event_list = "\n"
357786
+        s = stats.get()
357786
+        for key in s.keys():
357786
+            if key.find('(') != -1:
357786
+                key = key[0:key.find('(')]
357786
+            if event_list.find('\n' + key + '\n') == -1:
357786
+                event_list += key + '\n'
357786
+        sys.stdout.write(event_list)
357786
+        return ""
9bac43
+
9bac43
+    if options.log:
9bac43
+        log(stats)
9bac43
+    elif not options.once:
9bac43
+        with Tui(stats) as tui:
9bac43
+            tui.show_stats()
9bac43
+    else:
9bac43
+        batch(stats)
9bac43
+
9bac43
+if __name__ == "__main__":
9bac43
+    main()
9bac43
diff --git a/scripts/kvm/kvm_stat.texi b/scripts/kvm/kvm_stat.texi
9bac43
new file mode 100644
357786
index 0000000..5d964f6
9bac43
--- /dev/null
9bac43
+++ b/scripts/kvm/kvm_stat.texi
357786
@@ -0,0 +1,104 @@
9bac43
+@example
9bac43
+@c man begin SYNOPSIS
9bac43
+usage: kvm_stat [OPTION]...
9bac43
+@c man end
9bac43
+@end example
9bac43
+
9bac43
+@c man begin DESCRIPTION
9bac43
+
9bac43
+kvm_stat prints counts of KVM kernel module trace events.  These events signify
9bac43
+state transitions such as guest mode entry and exit.
9bac43
+
9bac43
+This tool is useful for observing guest behavior from the host perspective.
9bac43
+Often conclusions about performance or buggy behavior can be drawn from the
9bac43
+output.
9bac43
+
9bac43
+The set of KVM kernel module trace events may be specific to the kernel version
9bac43
+or architecture.  It is best to check the KVM kernel module source code for the
9bac43
+meaning of events.
9bac43
+
357786
+Use batch and logging modes for scripting purposes.
357786
+
357786
+@section Interactive Commands
357786
+
357786
+While running in regular (interactive) mode, use any of the following keys:
357786
+
357786
+@table @key
357786
+@item b
357786
+@kindex b
357786
+toggle events by guests (debugfs only, honors filters)
357786
+@item c
357786
+@kindex c
357786
+clear filter
357786
+@item f
357786
+@kindex f
357786
+filter by regular expression
357786
+@item g
357786
+@kindex g
357786
+filter by guest name
357786
+@item h
357786
+@kindex h
357786
+display interactive commands reference
357786
+@item o
357786
+@kindex o
357786
+toggle sorting order (Total vs CurAvg/s)
357786
+@item p
357786
+@kindex p
357786
+filter by PID
357786
+@item q
357786
+@kindex q
357786
+quit
357786
+@item r
357786
+@kindex r
357786
+reset stats
357786
+@item s
357786
+@kindex s
357786
+set update interval
357786
+@item x
357786
+@kindex x
357786
+toggle reporting of stats for child trace events
357786
+@end table
357786
+
357786
+Press any other key to refresh statistics immediately.
357786
+
9bac43
+@c man end
9bac43
+
357786
+
9bac43
+@c man begin OPTIONS
9bac43
+@table @option
9bac43
+@item -1, --once, --batch
9bac43
+  run in batch mode for one second
9bac43
+@item -l, --log
9bac43
+  run in logging mode (like vmstat)
9bac43
+@item -t, --tracepoints
9bac43
+  retrieve statistics from tracepoints
9bac43
+@item -d, --debugfs
9bac43
+  retrieve statistics from debugfs
9bac43
+@item -p, --pid=@var{pid}
9bac43
+  limit statistics to one virtual machine (pid)
357786
+@item -i, --debugfs-include-past
357786
+  include all available data on past events for debugfs
357786
+@item -g, --guest=@var{guest_name}
357786
+  limit statistics to one virtual machine (guest name)
9bac43
+@item -f, --fields=@var{fields}
9bac43
+  fields to display (regex)
9bac43
+@item -h, --help
9bac43
+  show help message
9bac43
+@end table
9bac43
+
9bac43
+@c man end
9bac43
+
9bac43
+@ignore
9bac43
+
9bac43
+@setfilename kvm_stat
9bac43
+@settitle Report KVM kernel module event counters.
9bac43
+
9bac43
+@c man begin AUTHOR
9bac43
+Stefan Hajnoczi <stefanha@redhat.com>
9bac43
+@c man end
9bac43
+
9bac43
+@c man begin SEEALSO
9bac43
+perf(1), trace-cmd(1)
9bac43
+@c man end
9bac43
+
9bac43
+@end ignore
9bac43
-- 
9bac43
1.8.3.1
9bac43