tools/kvm_stat: fix handling of invalid paths in debugfs provider
[linux-2.6-block.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_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) or
579                        ARCH.tracepoint_is_child(field)]
580
581     @staticmethod
582     def _get_online_cpus():
583         """Returns a list of cpu id integers."""
584         def parse_int_list(list_string):
585             """Returns an int list from a string of comma separated integers and
586             integer ranges."""
587             integers = []
588             members = list_string.split(',')
589
590             for member in members:
591                 if '-' not in member:
592                     integers.append(int(member))
593                 else:
594                     int_range = member.split('-')
595                     integers.extend(range(int(int_range[0]),
596                                           int(int_range[1]) + 1))
597
598             return integers
599
600         with open('/sys/devices/system/cpu/online') as cpu_list:
601             cpu_string = cpu_list.readline()
602             return parse_int_list(cpu_string)
603
604     def _setup_traces(self):
605         """Creates all event and group objects needed to be able to retrieve
606         data."""
607         fields = self._get_available_fields()
608         if self._pid > 0:
609             # Fetch list of all threads of the monitored pid, as qemu
610             # starts a thread for each vcpu.
611             path = os.path.join('/proc', str(self._pid), 'task')
612             groupids = self.walkdir(path)[1]
613         else:
614             groupids = self._get_online_cpus()
615
616         # The constant is needed as a buffer for python libs, std
617         # streams and other files that the script opens.
618         newlim = len(groupids) * len(fields) + 50
619         try:
620             softlim_, hardlim = resource.getrlimit(resource.RLIMIT_NOFILE)
621
622             if hardlim < newlim:
623                 # Now we need CAP_SYS_RESOURCE, to increase the hard limit.
624                 resource.setrlimit(resource.RLIMIT_NOFILE, (newlim, newlim))
625             else:
626                 # Raising the soft limit is sufficient.
627                 resource.setrlimit(resource.RLIMIT_NOFILE, (newlim, hardlim))
628
629         except ValueError:
630             sys.exit("NOFILE rlimit could not be raised to {0}".format(newlim))
631
632         for groupid in groupids:
633             group = Group()
634             for name in fields:
635                 tracepoint = name
636                 tracefilter = None
637                 match = re.match(r'(.*)\((.*)\)', name)
638                 if match:
639                     tracepoint, sub = match.groups()
640                     tracefilter = ('%s==%d\0' %
641                                    (self.filters[tracepoint][0],
642                                     self.filters[tracepoint][1][sub]))
643
644                 # From perf_event_open(2):
645                 # pid > 0 and cpu == -1
646                 # This measures the specified process/thread on any CPU.
647                 #
648                 # pid == -1 and cpu >= 0
649                 # This measures all processes/threads on the specified CPU.
650                 trace_cpu = groupid if self._pid == 0 else -1
651                 trace_pid = int(groupid) if self._pid != 0 else -1
652
653                 group.add_event(Event(name=name,
654                                       group=group,
655                                       trace_cpu=trace_cpu,
656                                       trace_pid=trace_pid,
657                                       trace_point=tracepoint,
658                                       trace_filter=tracefilter))
659
660             self.group_leaders.append(group)
661
662     @property
663     def fields(self):
664         return self._fields
665
666     @fields.setter
667     def fields(self, fields):
668         """Enables/disables the (un)wanted events"""
669         self._fields = fields
670         for group in self.group_leaders:
671             for index, event in enumerate(group.events):
672                 if event.name in fields:
673                     event.reset()
674                     event.enable()
675                 else:
676                     # Do not disable the group leader.
677                     # It would disable all of its events.
678                     if index != 0:
679                         event.disable()
680
681     @property
682     def pid(self):
683         return self._pid
684
685     @pid.setter
686     def pid(self, pid):
687         """Changes the monitored pid by setting new traces."""
688         self._pid = pid
689         # The garbage collector will get rid of all Event/Group
690         # objects and open files after removing the references.
691         self.group_leaders = []
692         self._setup_traces()
693         self.fields = self._fields
694
695     def read(self, by_guest=0):
696         """Returns 'event name: current value' for all enabled events."""
697         ret = defaultdict(int)
698         for group in self.group_leaders:
699             for name, val in group.read().items():
700                 if name not in self._fields:
701                     continue
702                 parent = ARCH.tracepoint_is_child(name)
703                 if parent:
704                     name += ' ' + parent
705                 ret[name] += val
706         return ret
707
708     def reset(self):
709         """Reset all field counters"""
710         for group in self.group_leaders:
711             for event in group.events:
712                 event.reset()
713
714
715 class DebugfsProvider(Provider):
716     """Provides data from the files that KVM creates in the kvm debugfs
717     folder."""
718     def __init__(self, pid, fields_filter, include_past):
719         self.update_fields(fields_filter)
720         self._baseline = {}
721         self.do_read = True
722         self.paths = []
723         super(DebugfsProvider, self).__init__(pid)
724         if include_past:
725             self._restore()
726
727     def _get_available_fields(self):
728         """"Returns a list of available fields.
729
730         The fields are all available KVM debugfs files
731
732         """
733         return self.walkdir(PATH_DEBUGFS_KVM)[2]
734
735     def update_fields(self, fields_filter):
736         """Refresh fields, applying fields_filter"""
737         self._fields = [field for field in self._get_available_fields()
738                         if self.is_field_wanted(fields_filter, field) or
739                         ARCH.debugfs_is_child(field)]
740
741     @property
742     def fields(self):
743         return self._fields
744
745     @fields.setter
746     def fields(self, fields):
747         self._fields = fields
748         self.reset()
749
750     @property
751     def pid(self):
752         return self._pid
753
754     @pid.setter
755     def pid(self, pid):
756         self._pid = pid
757         if pid != 0:
758             vms = self.walkdir(PATH_DEBUGFS_KVM)[1]
759             if len(vms) == 0:
760                 self.do_read = False
761
762             self.paths = list(filter(lambda x: "{}-".format(pid) in x, vms))
763
764         else:
765             self.paths = []
766             self.do_read = True
767         self.reset()
768
769     def _verify_paths(self):
770         """Remove invalid paths"""
771         for path in self.paths:
772             if not os.path.exists(os.path.join(PATH_DEBUGFS_KVM, path)):
773                 self.paths.remove(path)
774                 continue
775
776     def read(self, reset=0, by_guest=0):
777         """Returns a dict with format:'file name / field -> current value'.
778
779         Parameter 'reset':
780           0   plain read
781           1   reset field counts to 0
782           2   restore the original field counts
783
784         """
785         results = {}
786
787         # If no debugfs filtering support is available, then don't read.
788         if not self.do_read:
789             return results
790         self._verify_paths()
791
792         paths = self.paths
793         if self._pid == 0:
794             paths = []
795             for entry in os.walk(PATH_DEBUGFS_KVM):
796                 for dir in entry[1]:
797                     paths.append(dir)
798         for path in paths:
799             for field in self._fields:
800                 value = self._read_field(field, path)
801                 key = path + field
802                 if reset == 1:
803                     self._baseline[key] = value
804                 if reset == 2:
805                     self._baseline[key] = 0
806                 if self._baseline.get(key, -1) == -1:
807                     self._baseline[key] = value
808                 parent = ARCH.debugfs_is_child(field)
809                 if parent:
810                     field = field + ' ' + parent
811                 else:
812                     if by_guest:
813                         field = key.split('-')[0]    # set 'field' to 'pid'
814                 increment = value - self._baseline.get(key, 0)
815                 if field in results:
816                     results[field] += increment
817                 else:
818                     results[field] = increment
819
820         return results
821
822     def _read_field(self, field, path):
823         """Returns the value of a single field from a specific VM."""
824         try:
825             return int(open(os.path.join(PATH_DEBUGFS_KVM,
826                                          path,
827                                          field))
828                        .read())
829         except IOError:
830             return 0
831
832     def reset(self):
833         """Reset field counters"""
834         self._baseline = {}
835         self.read(1)
836
837     def _restore(self):
838         """Reset field counters"""
839         self._baseline = {}
840         self.read(2)
841
842
843 EventStat = namedtuple('EventStat', ['value', 'delta'])
844
845
846 class Stats(object):
847     """Manages the data providers and the data they provide.
848
849     It is used to set filters on the provider's data and collect all
850     provider data.
851
852     """
853     def __init__(self, options):
854         self.providers = self._get_providers(options)
855         self._pid_filter = options.pid
856         self._fields_filter = options.fields
857         self.values = {}
858         self._child_events = False
859
860     def _get_providers(self, options):
861         """Returns a list of data providers depending on the passed options."""
862         providers = []
863
864         if options.debugfs:
865             providers.append(DebugfsProvider(options.pid, options.fields,
866                                              options.dbgfs_include_past))
867         if options.tracepoints or not providers:
868             providers.append(TracepointProvider(options.pid, options.fields))
869
870         return providers
871
872     def _update_provider_filters(self):
873         """Propagates fields filters to providers."""
874         # As we reset the counters when updating the fields we can
875         # also clear the cache of old values.
876         self.values = {}
877         for provider in self.providers:
878             provider.update_fields(self._fields_filter)
879
880     def reset(self):
881         self.values = {}
882         for provider in self.providers:
883             provider.reset()
884
885     @property
886     def fields_filter(self):
887         return self._fields_filter
888
889     @fields_filter.setter
890     def fields_filter(self, fields_filter):
891         if fields_filter != self._fields_filter:
892             self._fields_filter = fields_filter
893             self._update_provider_filters()
894
895     @property
896     def pid_filter(self):
897         return self._pid_filter
898
899     @pid_filter.setter
900     def pid_filter(self, pid):
901         if pid != self._pid_filter:
902             self._pid_filter = pid
903             self.values = {}
904             for provider in self.providers:
905                 provider.pid = self._pid_filter
906
907     @property
908     def child_events(self):
909         return self._child_events
910
911     @child_events.setter
912     def child_events(self, val):
913         self._child_events = val
914         for provider in self.providers:
915             provider.child_events = val
916
917     def get(self, by_guest=0):
918         """Returns a dict with field -> (value, delta to last value) of all
919         provider data.
920         Key formats:
921           * plain: 'key' is event name
922           * child-parent: 'key' is in format '<child> <parent>'
923           * pid: 'key' is the pid of the guest, and the record contains the
924                aggregated event data
925         These formats are generated by the providers, and handled in class TUI.
926         """
927         for provider in self.providers:
928             new = provider.read(by_guest=by_guest)
929             for key in new:
930                 oldval = self.values.get(key, EventStat(0, 0)).value
931                 newval = new.get(key, 0)
932                 newdelta = newval - oldval
933                 self.values[key] = EventStat(newval, newdelta)
934         return self.values
935
936     def toggle_display_guests(self, to_pid):
937         """Toggle between collection of stats by individual event and by
938         guest pid
939
940         Events reported by DebugfsProvider change when switching to/from
941         reading by guest values. Hence we have to remove the excess event
942         names from self.values.
943
944         """
945         if any(isinstance(ins, TracepointProvider) for ins in self.providers):
946             return 1
947         if to_pid:
948             for provider in self.providers:
949                 if isinstance(provider, DebugfsProvider):
950                     for key in provider.fields:
951                         if key in self.values.keys():
952                             del self.values[key]
953         else:
954             oldvals = self.values.copy()
955             for key in oldvals:
956                 if key.isdigit():
957                     del self.values[key]
958         # Update oldval (see get())
959         self.get(to_pid)
960         return 0
961
962
963 DELAY_DEFAULT = 3.0
964 MAX_GUEST_NAME_LEN = 48
965 MAX_REGEX_LEN = 44
966 SORT_DEFAULT = 0
967
968
969 class Tui(object):
970     """Instruments curses to draw a nice text ui."""
971     def __init__(self, stats):
972         self.stats = stats
973         self.screen = None
974         self._delay_initial = 0.25
975         self._delay_regular = DELAY_DEFAULT
976         self._sorting = SORT_DEFAULT
977         self._display_guests = 0
978
979     def __enter__(self):
980         """Initialises curses for later use.  Based on curses.wrapper
981            implementation from the Python standard library."""
982         self.screen = curses.initscr()
983         curses.noecho()
984         curses.cbreak()
985
986         # The try/catch works around a minor bit of
987         # over-conscientiousness in the curses module, the error
988         # return from C start_color() is ignorable.
989         try:
990             curses.start_color()
991         except curses.error:
992             pass
993
994         # Hide cursor in extra statement as some monochrome terminals
995         # might support hiding but not colors.
996         try:
997             curses.curs_set(0)
998         except curses.error:
999             pass
1000
1001         curses.use_default_colors()
1002         return self
1003
1004     def __exit__(self, *exception):
1005         """Resets the terminal to its normal state.  Based on curses.wrapper
1006            implementation from the Python standard library."""
1007         if self.screen:
1008             self.screen.keypad(0)
1009             curses.echo()
1010             curses.nocbreak()
1011             curses.endwin()
1012
1013     @staticmethod
1014     def get_all_gnames():
1015         """Returns a list of (pid, gname) tuples of all running guests"""
1016         res = []
1017         try:
1018             child = subprocess.Popen(['ps', '-A', '--format', 'pid,args'],
1019                                      stdout=subprocess.PIPE)
1020         except:
1021             raise Exception
1022         for line in child.stdout:
1023             line = line.decode(ENCODING).lstrip().split(' ', 1)
1024             # perform a sanity check before calling the more expensive
1025             # function to possibly extract the guest name
1026             if ' -name ' in line[1]:
1027                 res.append((line[0], Tui.get_gname_from_pid(line[0])))
1028         child.stdout.close()
1029
1030         return res
1031
1032     def _print_all_gnames(self, row):
1033         """Print a list of all running guests along with their pids."""
1034         self.screen.addstr(row, 2, '%8s  %-60s' %
1035                            ('Pid', 'Guest Name (fuzzy list, might be '
1036                             'inaccurate!)'),
1037                            curses.A_UNDERLINE)
1038         row += 1
1039         try:
1040             for line in self.get_all_gnames():
1041                 self.screen.addstr(row, 2, '%8s  %-60s' % (line[0], line[1]))
1042                 row += 1
1043                 if row >= self.screen.getmaxyx()[0]:
1044                     break
1045         except Exception:
1046             self.screen.addstr(row + 1, 2, 'Not available')
1047
1048     @staticmethod
1049     def get_pid_from_gname(gname):
1050         """Fuzzy function to convert guest name to QEMU process pid.
1051
1052         Returns a list of potential pids, can be empty if no match found.
1053         Throws an exception on processing errors.
1054
1055         """
1056         pids = []
1057         for line in Tui.get_all_gnames():
1058             if gname == line[1]:
1059                 pids.append(int(line[0]))
1060
1061         return pids
1062
1063     @staticmethod
1064     def get_gname_from_pid(pid):
1065         """Returns the guest name for a QEMU process pid.
1066
1067         Extracts the guest name from the QEMU comma line by processing the
1068         '-name' option. Will also handle names specified out of sequence.
1069
1070         """
1071         name = ''
1072         try:
1073             line = open('/proc/{}/cmdline'
1074                         .format(pid), 'r').read().split('\0')
1075             parms = line[line.index('-name') + 1].split(',')
1076             while '' in parms:
1077                 # commas are escaped (i.e. ',,'), hence e.g. 'foo,bar' results
1078                 # in # ['foo', '', 'bar'], which we revert here
1079                 idx = parms.index('')
1080                 parms[idx - 1] += ',' + parms[idx + 1]
1081                 del parms[idx:idx+2]
1082             # the '-name' switch allows for two ways to specify the guest name,
1083             # where the plain name overrides the name specified via 'guest='
1084             for arg in parms:
1085                 if '=' not in arg:
1086                     name = arg
1087                     break
1088                 if arg[:6] == 'guest=':
1089                     name = arg[6:]
1090         except (ValueError, IOError, IndexError):
1091             pass
1092
1093         return name
1094
1095     def _update_pid(self, pid):
1096         """Propagates pid selection to stats object."""
1097         self.screen.addstr(4, 1, 'Updating pid filter...')
1098         self.screen.refresh()
1099         self.stats.pid_filter = pid
1100
1101     def _refresh_header(self, pid=None):
1102         """Refreshes the header."""
1103         if pid is None:
1104             pid = self.stats.pid_filter
1105         self.screen.erase()
1106         gname = self.get_gname_from_pid(pid)
1107         if gname:
1108             gname = ('({})'.format(gname[:MAX_GUEST_NAME_LEN] + '...'
1109                                    if len(gname) > MAX_GUEST_NAME_LEN
1110                                    else gname))
1111         if pid > 0:
1112             self.screen.addstr(0, 0, 'kvm statistics - pid {0} {1}'
1113                                .format(pid, gname), curses.A_BOLD)
1114         else:
1115             self.screen.addstr(0, 0, 'kvm statistics - summary', curses.A_BOLD)
1116         if self.stats.fields_filter:
1117             regex = self.stats.fields_filter
1118             if len(regex) > MAX_REGEX_LEN:
1119                 regex = regex[:MAX_REGEX_LEN] + '...'
1120             self.screen.addstr(1, 17, 'regex filter: {0}'.format(regex))
1121         if self._display_guests:
1122             col_name = 'Guest Name'
1123         else:
1124             col_name = 'Event'
1125         self.screen.addstr(2, 1, '%-40s %10s%7s %8s' %
1126                            (col_name, 'Total', '%Total', 'CurAvg/s'),
1127                            curses.A_STANDOUT)
1128         self.screen.addstr(4, 1, 'Collecting data...')
1129         self.screen.refresh()
1130
1131     def _refresh_body(self, sleeptime):
1132         def insert_child(sorted_items, child, values, parent):
1133             num = len(sorted_items)
1134             for i in range(0, num):
1135                 # only add child if parent is present
1136                 if parent.startswith(sorted_items[i][0]):
1137                     sorted_items.insert(i + 1, ('  ' + child, values))
1138
1139         def get_sorted_events(self, stats):
1140             """ separate parent and child events """
1141             if self._sorting == SORT_DEFAULT:
1142                 def sortkey(pair):
1143                     # sort by (delta value, overall value)
1144                     v = pair[1]
1145                     return (v.delta, v.value)
1146             else:
1147                 def sortkey(pair):
1148                     # sort by overall value
1149                     v = pair[1]
1150                     return v.value
1151
1152             childs = []
1153             sorted_items = []
1154             # we can't rule out child events to appear prior to parents even
1155             # when sorted - separate out all children first, and add in later
1156             for key, values in sorted(stats.items(), key=sortkey,
1157                                       reverse=True):
1158                 if values == (0, 0):
1159                     continue
1160                 if key.find(' ') != -1:
1161                     if not self.stats.child_events:
1162                         continue
1163                     childs.insert(0, (key, values))
1164                 else:
1165                     sorted_items.append((key, values))
1166             if self.stats.child_events:
1167                 for key, values in childs:
1168                     (child, parent) = key.split(' ')
1169                     insert_child(sorted_items, child, values, parent)
1170
1171             return sorted_items
1172
1173         row = 3
1174         self.screen.move(row, 0)
1175         self.screen.clrtobot()
1176         stats = self.stats.get(self._display_guests)
1177         total = 0.
1178         ctotal = 0.
1179         for key, values in stats.items():
1180             if self._display_guests:
1181                 if self.get_gname_from_pid(key):
1182                     total += values.value
1183                 continue
1184             if not key.find(' ') != -1:
1185                 total += values.value
1186             else:
1187                 ctotal += values.value
1188         if total == 0.:
1189             # we don't have any fields, or all non-child events are filtered
1190             total = ctotal
1191
1192         # print events
1193         tavg = 0
1194         tcur = 0
1195         for key, values in get_sorted_events(self, stats):
1196             if row >= self.screen.getmaxyx()[0] - 1 or values == (0, 0):
1197                 break
1198             if self._display_guests:
1199                 key = self.get_gname_from_pid(key)
1200                 if not key:
1201                     continue
1202             cur = int(round(values.delta / sleeptime)) if values.delta else ''
1203             if key[0] != ' ':
1204                 if values.delta:
1205                     tcur += values.delta
1206                 ptotal = values.value
1207                 ltotal = total
1208             else:
1209                 ltotal = ptotal
1210             self.screen.addstr(row, 1, '%-40s %10d%7.1f %8s' % (key,
1211                                values.value,
1212                                values.value * 100 / float(ltotal), cur))
1213             row += 1
1214         if row == 3:
1215             self.screen.addstr(4, 1, 'No matching events reported yet')
1216         if row > 4:
1217             tavg = int(round(tcur / sleeptime)) if tcur > 0 else ''
1218             self.screen.addstr(row, 1, '%-40s %10d        %8s' %
1219                                ('Total', total, tavg), curses.A_BOLD)
1220         self.screen.refresh()
1221
1222     def _show_msg(self, text):
1223         """Display message centered text and exit on key press"""
1224         hint = 'Press any key to continue'
1225         curses.cbreak()
1226         self.screen.erase()
1227         (x, term_width) = self.screen.getmaxyx()
1228         row = 2
1229         for line in text:
1230             start = (term_width - len(line)) // 2
1231             self.screen.addstr(row, start, line)
1232             row += 1
1233         self.screen.addstr(row + 1, (term_width - len(hint)) // 2, hint,
1234                            curses.A_STANDOUT)
1235         self.screen.getkey()
1236
1237     def _show_help_interactive(self):
1238         """Display help with list of interactive commands"""
1239         msg = ('   b     toggle events by guests (debugfs only, honors'
1240                ' filters)',
1241                '   c     clear filter',
1242                '   f     filter by regular expression',
1243                '   g     filter by guest name/PID',
1244                '   h     display interactive commands reference',
1245                '   o     toggle sorting order (Total vs CurAvg/s)',
1246                '   p     filter by guest name/PID',
1247                '   q     quit',
1248                '   r     reset stats',
1249                '   s     set update interval',
1250                '   x     toggle reporting of stats for individual child trace'
1251                ' events',
1252                'Any other key refreshes statistics immediately')
1253         curses.cbreak()
1254         self.screen.erase()
1255         self.screen.addstr(0, 0, "Interactive commands reference",
1256                            curses.A_BOLD)
1257         self.screen.addstr(2, 0, "Press any key to exit", curses.A_STANDOUT)
1258         row = 4
1259         for line in msg:
1260             self.screen.addstr(row, 0, line)
1261             row += 1
1262         self.screen.getkey()
1263         self._refresh_header()
1264
1265     def _show_filter_selection(self):
1266         """Draws filter selection mask.
1267
1268         Asks for a valid regex and sets the fields filter accordingly.
1269
1270         """
1271         msg = ''
1272         while True:
1273             self.screen.erase()
1274             self.screen.addstr(0, 0,
1275                                "Show statistics for events matching a regex.",
1276                                curses.A_BOLD)
1277             self.screen.addstr(2, 0,
1278                                "Current regex: {0}"
1279                                .format(self.stats.fields_filter))
1280             self.screen.addstr(5, 0, msg)
1281             self.screen.addstr(3, 0, "New regex: ")
1282             curses.echo()
1283             regex = self.screen.getstr().decode(ENCODING)
1284             curses.noecho()
1285             if len(regex) == 0:
1286                 self.stats.fields_filter = ''
1287                 self._refresh_header()
1288                 return
1289             try:
1290                 re.compile(regex)
1291                 self.stats.fields_filter = regex
1292                 self._refresh_header()
1293                 return
1294             except re.error:
1295                 msg = '"' + regex + '": Not a valid regular expression'
1296                 continue
1297
1298     def _show_set_update_interval(self):
1299         """Draws update interval selection mask."""
1300         msg = ''
1301         while True:
1302             self.screen.erase()
1303             self.screen.addstr(0, 0, 'Set update interval (defaults to %fs).' %
1304                                DELAY_DEFAULT, curses.A_BOLD)
1305             self.screen.addstr(4, 0, msg)
1306             self.screen.addstr(2, 0, 'Change delay from %.1fs to ' %
1307                                self._delay_regular)
1308             curses.echo()
1309             val = self.screen.getstr().decode(ENCODING)
1310             curses.noecho()
1311
1312             try:
1313                 if len(val) > 0:
1314                     delay = float(val)
1315                     if delay < 0.1:
1316                         msg = '"' + str(val) + '": Value must be >=0.1'
1317                         continue
1318                     if delay > 25.5:
1319                         msg = '"' + str(val) + '": Value must be <=25.5'
1320                         continue
1321                 else:
1322                     delay = DELAY_DEFAULT
1323                 self._delay_regular = delay
1324                 break
1325
1326             except ValueError:
1327                 msg = '"' + str(val) + '": Invalid value'
1328         self._refresh_header()
1329
1330     def _show_vm_selection_by_guest(self):
1331         """Draws guest selection mask.
1332
1333         Asks for a guest name or pid until a valid guest name or '' is entered.
1334
1335         """
1336         msg = ''
1337         while True:
1338             self.screen.erase()
1339             self.screen.addstr(0, 0,
1340                                'Show statistics for specific guest or pid.',
1341                                curses.A_BOLD)
1342             self.screen.addstr(1, 0,
1343                                'This might limit the shown data to the trace '
1344                                'statistics.')
1345             self.screen.addstr(5, 0, msg)
1346             self._print_all_gnames(7)
1347             curses.echo()
1348             curses.curs_set(1)
1349             self.screen.addstr(3, 0, "Guest or pid [ENTER exits]: ")
1350             guest = self.screen.getstr().decode(ENCODING)
1351             curses.noecho()
1352
1353             pid = 0
1354             if not guest or guest == '0':
1355                 break
1356             if guest.isdigit():
1357                 if not os.path.isdir(os.path.join('/proc/', guest)):
1358                     msg = '"' + guest + '": Not a running process'
1359                     continue
1360                 pid = int(guest)
1361                 break
1362             pids = []
1363             try:
1364                 pids = self.get_pid_from_gname(guest)
1365             except:
1366                 msg = '"' + guest + '": Internal error while searching, ' \
1367                       'use pid filter instead'
1368                 continue
1369             if len(pids) == 0:
1370                 msg = '"' + guest + '": Not an active guest'
1371                 continue
1372             if len(pids) > 1:
1373                 msg = '"' + guest + '": Multiple matches found, use pid ' \
1374                       'filter instead'
1375                 continue
1376             pid = pids[0]
1377             break
1378         curses.curs_set(0)
1379         self._refresh_header(pid)
1380         self._update_pid(pid)
1381
1382     def show_stats(self):
1383         """Refreshes the screen and processes user input."""
1384         sleeptime = self._delay_initial
1385         self._refresh_header()
1386         start = 0.0  # result based on init value never appears on screen
1387         while True:
1388             self._refresh_body(time.time() - start)
1389             curses.halfdelay(int(sleeptime * 10))
1390             start = time.time()
1391             sleeptime = self._delay_regular
1392             try:
1393                 char = self.screen.getkey()
1394                 if char == 'b':
1395                     self._display_guests = not self._display_guests
1396                     if self.stats.toggle_display_guests(self._display_guests):
1397                         self._show_msg(['Command not available with '
1398                                         'tracepoints enabled', 'Restart with '
1399                                         'debugfs only (see option \'-d\') and '
1400                                         'try again!'])
1401                         self._display_guests = not self._display_guests
1402                     self._refresh_header()
1403                 if char == 'c':
1404                     self.stats.fields_filter = ''
1405                     self._refresh_header(0)
1406                     self._update_pid(0)
1407                 if char == 'f':
1408                     curses.curs_set(1)
1409                     self._show_filter_selection()
1410                     curses.curs_set(0)
1411                     sleeptime = self._delay_initial
1412                 if char == 'g' or char == 'p':
1413                     self._show_vm_selection_by_guest()
1414                     sleeptime = self._delay_initial
1415                 if char == 'h':
1416                     self._show_help_interactive()
1417                 if char == 'o':
1418                     self._sorting = not self._sorting
1419                 if char == 'q':
1420                     break
1421                 if char == 'r':
1422                     self.stats.reset()
1423                 if char == 's':
1424                     curses.curs_set(1)
1425                     self._show_set_update_interval()
1426                     curses.curs_set(0)
1427                     sleeptime = self._delay_initial
1428                 if char == 'x':
1429                     self.stats.child_events = not self.stats.child_events
1430             except KeyboardInterrupt:
1431                 break
1432             except curses.error:
1433                 continue
1434
1435
1436 def batch(stats):
1437     """Prints statistics in a key, value format."""
1438     try:
1439         s = stats.get()
1440         time.sleep(1)
1441         s = stats.get()
1442         for key, values in sorted(s.items()):
1443             print('%-42s%10d%10d' % (key.split(' ')[0], values.value,
1444                   values.delta))
1445     except KeyboardInterrupt:
1446         pass
1447
1448
1449 def log(stats):
1450     """Prints statistics as reiterating key block, multiple value blocks."""
1451     keys = sorted(stats.get().keys())
1452
1453     def banner():
1454         for key in keys:
1455             print(key.split(' ')[0], end=' ')
1456         print()
1457
1458     def statline():
1459         s = stats.get()
1460         for key in keys:
1461             print(' %9d' % s[key].delta, end=' ')
1462         print()
1463     line = 0
1464     banner_repeat = 20
1465     while True:
1466         try:
1467             time.sleep(1)
1468             if line % banner_repeat == 0:
1469                 banner()
1470             statline()
1471             line += 1
1472         except KeyboardInterrupt:
1473             break
1474
1475
1476 def get_options():
1477     """Returns processed program arguments."""
1478     description_text = """
1479 This script displays various statistics about VMs running under KVM.
1480 The statistics are gathered from the KVM debugfs entries and / or the
1481 currently available perf traces.
1482
1483 The monitoring takes additional cpu cycles and might affect the VM's
1484 performance.
1485
1486 Requirements:
1487 - Access to:
1488     %s
1489     %s/events/*
1490     /proc/pid/task
1491 - /proc/sys/kernel/perf_event_paranoid < 1 if user has no
1492   CAP_SYS_ADMIN and perf events are used.
1493 - CAP_SYS_RESOURCE if the hard limit is not high enough to allow
1494   the large number of files that are possibly opened.
1495
1496 Interactive Commands:
1497    b     toggle events by guests (debugfs only, honors filters)
1498    c     clear filter
1499    f     filter by regular expression
1500    g     filter by guest name
1501    h     display interactive commands reference
1502    o     toggle sorting order (Total vs CurAvg/s)
1503    p     filter by PID
1504    q     quit
1505    r     reset stats
1506    s     set update interval
1507    x     toggle reporting of stats for individual child trace events
1508 Press any other key to refresh statistics immediately.
1509 """ % (PATH_DEBUGFS_KVM, PATH_DEBUGFS_TRACING)
1510
1511     class PlainHelpFormatter(optparse.IndentedHelpFormatter):
1512         def format_description(self, description):
1513             if description:
1514                 return description + "\n"
1515             else:
1516                 return ""
1517
1518     def cb_guest_to_pid(option, opt, val, parser):
1519         try:
1520             pids = Tui.get_pid_from_gname(val)
1521         except:
1522             sys.exit('Error while searching for guest "{}". Use "-p" to '
1523                      'specify a pid instead?'.format(val))
1524         if len(pids) == 0:
1525             sys.exit('Error: No guest by the name "{}" found'.format(val))
1526         if len(pids) > 1:
1527             sys.exit('Error: Multiple processes found (pids: {}). Use "-p" '
1528                      'to specify the desired pid'.format(" ".join(pids)))
1529         parser.values.pid = pids[0]
1530
1531     optparser = optparse.OptionParser(description=description_text,
1532                                       formatter=PlainHelpFormatter())
1533     optparser.add_option('-1', '--once', '--batch',
1534                          action='store_true',
1535                          default=False,
1536                          dest='once',
1537                          help='run in batch mode for one second',
1538                          )
1539     optparser.add_option('-i', '--debugfs-include-past',
1540                          action='store_true',
1541                          default=False,
1542                          dest='dbgfs_include_past',
1543                          help='include all available data on past events for '
1544                               'debugfs',
1545                          )
1546     optparser.add_option('-l', '--log',
1547                          action='store_true',
1548                          default=False,
1549                          dest='log',
1550                          help='run in logging mode (like vmstat)',
1551                          )
1552     optparser.add_option('-t', '--tracepoints',
1553                          action='store_true',
1554                          default=False,
1555                          dest='tracepoints',
1556                          help='retrieve statistics from tracepoints',
1557                          )
1558     optparser.add_option('-d', '--debugfs',
1559                          action='store_true',
1560                          default=False,
1561                          dest='debugfs',
1562                          help='retrieve statistics from debugfs',
1563                          )
1564     optparser.add_option('-f', '--fields',
1565                          action='store',
1566                          default='',
1567                          dest='fields',
1568                          help='''fields to display (regex)
1569                                  "-f help" for a list of available events''',
1570                          )
1571     optparser.add_option('-p', '--pid',
1572                          action='store',
1573                          default=0,
1574                          type='int',
1575                          dest='pid',
1576                          help='restrict statistics to pid',
1577                          )
1578     optparser.add_option('-g', '--guest',
1579                          action='callback',
1580                          type='string',
1581                          dest='pid',
1582                          metavar='GUEST',
1583                          help='restrict statistics to guest by name',
1584                          callback=cb_guest_to_pid,
1585                          )
1586     options, unkn = optparser.parse_args(sys.argv)
1587     if len(unkn) != 1:
1588         sys.exit('Error: Extra argument(s): ' + ' '.join(unkn[1:]))
1589     try:
1590         # verify that we were passed a valid regex up front
1591         re.compile(options.fields)
1592     except re.error:
1593         sys.exit('Error: "' + options.fields + '" is not a valid regular '
1594                  'expression')
1595
1596     return options
1597
1598
1599 def check_access(options):
1600     """Exits if the current user can't access all needed directories."""
1601     if not os.path.exists(PATH_DEBUGFS_TRACING) and (options.tracepoints or
1602                                                      not options.debugfs):
1603         sys.stderr.write("Please enable CONFIG_TRACING in your kernel "
1604                          "when using the option -t (default).\n"
1605                          "If it is enabled, make {0} readable by the "
1606                          "current user.\n"
1607                          .format(PATH_DEBUGFS_TRACING))
1608         if options.tracepoints:
1609             sys.exit(1)
1610
1611         sys.stderr.write("Falling back to debugfs statistics!\n")
1612         options.debugfs = True
1613         time.sleep(5)
1614
1615     return options
1616
1617
1618 def assign_globals():
1619     global PATH_DEBUGFS_KVM
1620     global PATH_DEBUGFS_TRACING
1621
1622     debugfs = ''
1623     for line in open('/proc/mounts'):
1624         if line.split(' ')[0] == 'debugfs':
1625             debugfs = line.split(' ')[1]
1626             break
1627     if debugfs == '':
1628         sys.stderr.write("Please make sure that CONFIG_DEBUG_FS is enabled in "
1629                          "your kernel, mounted and\nreadable by the current "
1630                          "user:\n"
1631                          "('mount -t debugfs debugfs /sys/kernel/debug')\n")
1632         sys.exit(1)
1633
1634     PATH_DEBUGFS_KVM = os.path.join(debugfs, 'kvm')
1635     PATH_DEBUGFS_TRACING = os.path.join(debugfs, 'tracing')
1636
1637     if not os.path.exists(PATH_DEBUGFS_KVM):
1638         sys.stderr.write("Please make sure that CONFIG_KVM is enabled in "
1639                          "your kernel and that the modules are loaded.\n")
1640         sys.exit(1)
1641
1642
1643 def main():
1644     assign_globals()
1645     options = get_options()
1646     options = check_access(options)
1647
1648     if (options.pid > 0 and
1649         not os.path.isdir(os.path.join('/proc/',
1650                                        str(options.pid)))):
1651         sys.stderr.write('Did you use a (unsupported) tid instead of a pid?\n')
1652         sys.exit('Specified pid does not exist.')
1653
1654     stats = Stats(options)
1655
1656     if options.fields == 'help':
1657         stats.fields_filter = None
1658         event_list = []
1659         for key in stats.get().keys():
1660             event_list.append(key.split('(', 1)[0])
1661         sys.stdout.write('  ' + '\n  '.join(sorted(set(event_list))) + '\n')
1662         sys.exit(0)
1663
1664     if options.log:
1665         log(stats)
1666     elif not options.once:
1667         with Tui(stats) as tui:
1668             tui.show_stats()
1669     else:
1670         batch(stats)
1671
1672 if __name__ == "__main__":
1673     main()