• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
15Three 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
20The data is sampled from the KVM's debugfs entries and its perf events.
21"""
22from __future__ import print_function
23
24import curses
25import sys
26import locale
27import os
28import time
29import optparse
30import ctypes
31import fcntl
32import resource
33import struct
34import re
35import subprocess
36from collections import defaultdict, namedtuple
37
38VMX_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
80SVM_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)
156AARCH64_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
195USERSPACE_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
223IOCTL_NUMBERS = {
224    'SET_FILTER':  0x40082406,
225    'ENABLE':      0x00002400,
226    'DISABLE':     0x00002401,
227    'RESET':       0x00002403,
228}
229
230ENCODING = locale.getpreferredencoding(False)
231TRACE_FILTER = re.compile(r'^[^\(]*$')
232
233
234class 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
270class 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
282class 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
302class 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
314class 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
327ARCH = Arch.get_arch()
328
329
330class 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
359PERF_TYPE_TRACEPOINT = 2
360PERF_FORMAT_GROUP = 1 << 3
361
362
363class 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
396class 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
501class 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
524class 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
723class 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
854EventStat = namedtuple('EventStat', ['value', 'delta'])
855
856
857class 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
974DELAY_DEFAULT = 3.0
975MAX_GUEST_NAME_LEN = 48
976MAX_REGEX_LEN = 44
977SORT_DEFAULT = 0
978
979
980class 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
1479def 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
1492def 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
1519def get_options():
1520    """Returns processed program arguments."""
1521    description_text = """
1522This script displays various statistics about VMs running under KVM.
1523The statistics are gathered from the KVM debugfs entries and / or the
1524currently available perf traces.
1525
1526The monitoring takes additional cpu cycles and might affect the VM's
1527performance.
1528
1529Requirements:
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
1539Interactive 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
1551Press 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
1642def 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
1661def 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
1686def 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
1715if __name__ == "__main__":
1716    main()
1717