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