|
|
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 |
|