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