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