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