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