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