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