]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blob - tools/kvm/kvm_stat/kvm_stat
4e0f282c52896489fcdb5d25974d07a80ec7531e
[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
232
233 class Arch(object):
234 """Encapsulates global architecture specific data.
235
236 Contains the performance event open syscall and ioctl numbers, as
237 well as the VM exit reasons for the architecture it runs on.
238
239 """
240 @staticmethod
241 def get_arch():
242 machine = os.uname()[4]
243
244 if machine.startswith('ppc'):
245 return ArchPPC()
246 elif machine.startswith('aarch64'):
247 return ArchA64()
248 elif machine.startswith('s390'):
249 return ArchS390()
250 else:
251 # X86_64
252 for line in open('/proc/cpuinfo'):
253 if not line.startswith('flags'):
254 continue
255
256 flags = line.split()
257 if 'vmx' in flags:
258 return ArchX86(VMX_EXIT_REASONS)
259 if 'svm' in flags:
260 return ArchX86(SVM_EXIT_REASONS)
261 return
262
263
264 class ArchX86(Arch):
265 def __init__(self, exit_reasons):
266 self.sc_perf_evt_open = 298
267 self.ioctl_numbers = IOCTL_NUMBERS
268 self.exit_reasons = exit_reasons
269
270
271 class ArchPPC(Arch):
272 def __init__(self):
273 self.sc_perf_evt_open = 319
274 self.ioctl_numbers = IOCTL_NUMBERS
275 self.ioctl_numbers['ENABLE'] = 0x20002400
276 self.ioctl_numbers['DISABLE'] = 0x20002401
277 self.ioctl_numbers['RESET'] = 0x20002403
278
279 # PPC comes in 32 and 64 bit and some generated ioctl
280 # numbers depend on the wordsize.
281 char_ptr_size = ctypes.sizeof(ctypes.c_char_p)
282 self.ioctl_numbers['SET_FILTER'] = 0x80002406 | char_ptr_size << 16
283 self.exit_reasons = {}
284
285
286 class ArchA64(Arch):
287 def __init__(self):
288 self.sc_perf_evt_open = 241
289 self.ioctl_numbers = IOCTL_NUMBERS
290 self.exit_reasons = AARCH64_EXIT_REASONS
291
292
293 class ArchS390(Arch):
294 def __init__(self):
295 self.sc_perf_evt_open = 331
296 self.ioctl_numbers = IOCTL_NUMBERS
297 self.exit_reasons = None
298
299 ARCH = Arch.get_arch()
300
301
302 class perf_event_attr(ctypes.Structure):
303 """Struct that holds the necessary data to set up a trace event.
304
305 For an extensive explanation see perf_event_open(2) and
306 include/uapi/linux/perf_event.h, struct perf_event_attr
307
308 All fields that are not initialized in the constructor are 0.
309
310 """
311 _fields_ = [('type', ctypes.c_uint32),
312 ('size', ctypes.c_uint32),
313 ('config', ctypes.c_uint64),
314 ('sample_freq', ctypes.c_uint64),
315 ('sample_type', ctypes.c_uint64),
316 ('read_format', ctypes.c_uint64),
317 ('flags', ctypes.c_uint64),
318 ('wakeup_events', ctypes.c_uint32),
319 ('bp_type', ctypes.c_uint32),
320 ('bp_addr', ctypes.c_uint64),
321 ('bp_len', ctypes.c_uint64),
322 ]
323
324 def __init__(self):
325 super(self.__class__, self).__init__()
326 self.type = PERF_TYPE_TRACEPOINT
327 self.size = ctypes.sizeof(self)
328 self.read_format = PERF_FORMAT_GROUP
329
330
331 PERF_TYPE_TRACEPOINT = 2
332 PERF_FORMAT_GROUP = 1 << 3
333
334 PATH_DEBUGFS_TRACING = '/sys/kernel/debug/tracing'
335 PATH_DEBUGFS_KVM = '/sys/kernel/debug/kvm'
336
337
338 class Group(object):
339 """Represents a perf event group."""
340
341 def __init__(self):
342 self.events = []
343
344 def add_event(self, event):
345 self.events.append(event)
346
347 def read(self):
348 """Returns a dict with 'event name: value' for all events in the
349 group.
350
351 Values are read by reading from the file descriptor of the
352 event that is the group leader. See perf_event_open(2) for
353 details.
354
355 Read format for the used event configuration is:
356 struct read_format {
357 u64 nr; /* The number of events */
358 struct {
359 u64 value; /* The value of the event */
360 } values[nr];
361 };
362
363 """
364 length = 8 * (1 + len(self.events))
365 read_format = 'xxxxxxxx' + 'Q' * len(self.events)
366 return dict(zip([event.name for event in self.events],
367 struct.unpack(read_format,
368 os.read(self.events[0].fd, length))))
369
370
371 class Event(object):
372 """Represents a performance event and manages its life cycle."""
373 def __init__(self, name, group, trace_cpu, trace_pid, trace_point,
374 trace_filter, trace_set='kvm'):
375 self.libc = ctypes.CDLL('libc.so.6', use_errno=True)
376 self.syscall = self.libc.syscall
377 self.name = name
378 self.fd = None
379 self.setup_event(group, trace_cpu, trace_pid, trace_point,
380 trace_filter, trace_set)
381
382 def __del__(self):
383 """Closes the event's file descriptor.
384
385 As no python file object was created for the file descriptor,
386 python will not reference count the descriptor and will not
387 close it itself automatically, so we do it.
388
389 """
390 if self.fd:
391 os.close(self.fd)
392
393 def perf_event_open(self, attr, pid, cpu, group_fd, flags):
394 """Wrapper for the sys_perf_evt_open() syscall.
395
396 Used to set up performance events, returns a file descriptor or -1
397 on error.
398
399 Attributes are:
400 - syscall number
401 - struct perf_event_attr *
402 - pid or -1 to monitor all pids
403 - cpu number or -1 to monitor all cpus
404 - The file descriptor of the group leader or -1 to create a group.
405 - flags
406
407 """
408 return self.syscall(ARCH.sc_perf_evt_open, ctypes.pointer(attr),
409 ctypes.c_int(pid), ctypes.c_int(cpu),
410 ctypes.c_int(group_fd), ctypes.c_long(flags))
411
412 def setup_event_attribute(self, trace_set, trace_point):
413 """Returns an initialized ctype perf_event_attr struct."""
414
415 id_path = os.path.join(PATH_DEBUGFS_TRACING, 'events', trace_set,
416 trace_point, 'id')
417
418 event_attr = perf_event_attr()
419 event_attr.config = int(open(id_path).read())
420 return event_attr
421
422 def setup_event(self, group, trace_cpu, trace_pid, trace_point,
423 trace_filter, trace_set):
424 """Sets up the perf event in Linux.
425
426 Issues the syscall to register the event in the kernel and
427 then sets the optional filter.
428
429 """
430
431 event_attr = self.setup_event_attribute(trace_set, trace_point)
432
433 # First event will be group leader.
434 group_leader = -1
435
436 # All others have to pass the leader's descriptor instead.
437 if group.events:
438 group_leader = group.events[0].fd
439
440 fd = self.perf_event_open(event_attr, trace_pid,
441 trace_cpu, group_leader, 0)
442 if fd == -1:
443 err = ctypes.get_errno()
444 raise OSError(err, os.strerror(err),
445 'while calling sys_perf_event_open().')
446
447 if trace_filter:
448 fcntl.ioctl(fd, ARCH.ioctl_numbers['SET_FILTER'],
449 trace_filter)
450
451 self.fd = fd
452
453 def enable(self):
454 """Enables the trace event in the kernel.
455
456 Enabling the group leader makes reading counters from it and the
457 events under it possible.
458
459 """
460 fcntl.ioctl(self.fd, ARCH.ioctl_numbers['ENABLE'], 0)
461
462 def disable(self):
463 """Disables the trace event in the kernel.
464
465 Disabling the group leader makes reading all counters under it
466 impossible.
467
468 """
469 fcntl.ioctl(self.fd, ARCH.ioctl_numbers['DISABLE'], 0)
470
471 def reset(self):
472 """Resets the count of the trace event in the kernel."""
473 fcntl.ioctl(self.fd, ARCH.ioctl_numbers['RESET'], 0)
474
475
476 class Provider(object):
477 """Encapsulates functionalities used by all providers."""
478 @staticmethod
479 def is_field_wanted(fields_filter, field):
480 """Indicate whether field is valid according to fields_filter."""
481 if not fields_filter:
482 return True
483 return re.match(fields_filter, field) is not None
484
485 @staticmethod
486 def walkdir(path):
487 """Returns os.walk() data for specified directory.
488
489 As it is only a wrapper it returns the same 3-tuple of (dirpath,
490 dirnames, filenames).
491 """
492 return next(os.walk(path))
493
494
495 class TracepointProvider(Provider):
496 """Data provider for the stats class.
497
498 Manages the events/groups from which it acquires its data.
499
500 """
501 def __init__(self, pid, fields_filter):
502 self.group_leaders = []
503 self.filters = self.get_filters()
504 self.update_fields(fields_filter)
505 self.pid = pid
506
507 @staticmethod
508 def get_filters():
509 """Returns a dict of trace events, their filter ids and
510 the values that can be filtered.
511
512 Trace events can be filtered for special values by setting a
513 filter string via an ioctl. The string normally has the format
514 identifier==value. For each filter a new event will be created, to
515 be able to distinguish the events.
516
517 """
518 filters = {}
519 filters['kvm_userspace_exit'] = ('reason', USERSPACE_EXIT_REASONS)
520 if ARCH.exit_reasons:
521 filters['kvm_exit'] = ('exit_reason', ARCH.exit_reasons)
522 return filters
523
524 def get_available_fields(self):
525 """Returns a list of available event's of format 'event name(filter
526 name)'.
527
528 All available events have directories under
529 /sys/kernel/debug/tracing/events/ which export information
530 about the specific event. Therefore, listing the dirs gives us
531 a list of all available events.
532
533 Some events like the vm exit reasons can be filtered for
534 specific values. To take account for that, the routine below
535 creates special fields with the following format:
536 event name(filter name)
537
538 """
539 path = os.path.join(PATH_DEBUGFS_TRACING, 'events', 'kvm')
540 fields = self.walkdir(path)[1]
541 extra = []
542 for field in fields:
543 if field in self.filters:
544 filter_name_, filter_dicts = self.filters[field]
545 for name in filter_dicts:
546 extra.append(field + '(' + name + ')')
547 fields += extra
548 return fields
549
550 def update_fields(self, fields_filter):
551 """Refresh fields, applying fields_filter"""
552 self.fields = [field for field in self.get_available_fields()
553 if self.is_field_wanted(fields_filter, field)]
554
555 @staticmethod
556 def get_online_cpus():
557 """Returns a list of cpu id integers."""
558 def parse_int_list(list_string):
559 """Returns an int list from a string of comma separated integers and
560 integer ranges."""
561 integers = []
562 members = list_string.split(',')
563
564 for member in members:
565 if '-' not in member:
566 integers.append(int(member))
567 else:
568 int_range = member.split('-')
569 integers.extend(range(int(int_range[0]),
570 int(int_range[1]) + 1))
571
572 return integers
573
574 with open('/sys/devices/system/cpu/online') as cpu_list:
575 cpu_string = cpu_list.readline()
576 return parse_int_list(cpu_string)
577
578 def setup_traces(self):
579 """Creates all event and group objects needed to be able to retrieve
580 data."""
581 fields = self.get_available_fields()
582 if self._pid > 0:
583 # Fetch list of all threads of the monitored pid, as qemu
584 # starts a thread for each vcpu.
585 path = os.path.join('/proc', str(self._pid), 'task')
586 groupids = self.walkdir(path)[1]
587 else:
588 groupids = self.get_online_cpus()
589
590 # The constant is needed as a buffer for python libs, std
591 # streams and other files that the script opens.
592 newlim = len(groupids) * len(fields) + 50
593 try:
594 softlim_, hardlim = resource.getrlimit(resource.RLIMIT_NOFILE)
595
596 if hardlim < newlim:
597 # Now we need CAP_SYS_RESOURCE, to increase the hard limit.
598 resource.setrlimit(resource.RLIMIT_NOFILE, (newlim, newlim))
599 else:
600 # Raising the soft limit is sufficient.
601 resource.setrlimit(resource.RLIMIT_NOFILE, (newlim, hardlim))
602
603 except ValueError:
604 sys.exit("NOFILE rlimit could not be raised to {0}".format(newlim))
605
606 for groupid in groupids:
607 group = Group()
608 for name in fields:
609 tracepoint = name
610 tracefilter = None
611 match = re.match(r'(.*)\((.*)\)', name)
612 if match:
613 tracepoint, sub = match.groups()
614 tracefilter = ('%s==%d\0' %
615 (self.filters[tracepoint][0],
616 self.filters[tracepoint][1][sub]))
617
618 # From perf_event_open(2):
619 # pid > 0 and cpu == -1
620 # This measures the specified process/thread on any CPU.
621 #
622 # pid == -1 and cpu >= 0
623 # This measures all processes/threads on the specified CPU.
624 trace_cpu = groupid if self._pid == 0 else -1
625 trace_pid = int(groupid) if self._pid != 0 else -1
626
627 group.add_event(Event(name=name,
628 group=group,
629 trace_cpu=trace_cpu,
630 trace_pid=trace_pid,
631 trace_point=tracepoint,
632 trace_filter=tracefilter))
633
634 self.group_leaders.append(group)
635
636 @property
637 def fields(self):
638 return self._fields
639
640 @fields.setter
641 def fields(self, fields):
642 """Enables/disables the (un)wanted events"""
643 self._fields = fields
644 for group in self.group_leaders:
645 for index, event in enumerate(group.events):
646 if event.name in fields:
647 event.reset()
648 event.enable()
649 else:
650 # Do not disable the group leader.
651 # It would disable all of its events.
652 if index != 0:
653 event.disable()
654
655 @property
656 def pid(self):
657 return self._pid
658
659 @pid.setter
660 def pid(self, pid):
661 """Changes the monitored pid by setting new traces."""
662 self._pid = pid
663 # The garbage collector will get rid of all Event/Group
664 # objects and open files after removing the references.
665 self.group_leaders = []
666 self.setup_traces()
667 self.fields = self._fields
668
669 def read(self, by_guest=0):
670 """Returns 'event name: current value' for all enabled events."""
671 ret = defaultdict(int)
672 for group in self.group_leaders:
673 for name, val in group.read().items():
674 if name in self._fields:
675 ret[name] += val
676 return ret
677
678 def reset(self):
679 """Reset all field counters"""
680 for group in self.group_leaders:
681 for event in group.events:
682 event.reset()
683
684
685 class DebugfsProvider(Provider):
686 """Provides data from the files that KVM creates in the kvm debugfs
687 folder."""
688 def __init__(self, pid, fields_filter, include_past):
689 self.update_fields(fields_filter)
690 self._baseline = {}
691 self.do_read = True
692 self.paths = []
693 self.pid = pid
694 if include_past:
695 self.restore()
696
697 def get_available_fields(self):
698 """"Returns a list of available fields.
699
700 The fields are all available KVM debugfs files
701
702 """
703 return self.walkdir(PATH_DEBUGFS_KVM)[2]
704
705 def update_fields(self, fields_filter):
706 """Refresh fields, applying fields_filter"""
707 self._fields = [field for field in self.get_available_fields()
708 if self.is_field_wanted(fields_filter, field)]
709
710 @property
711 def fields(self):
712 return self._fields
713
714 @fields.setter
715 def fields(self, fields):
716 self._fields = fields
717 self.reset()
718
719 @property
720 def pid(self):
721 return self._pid
722
723 @pid.setter
724 def pid(self, pid):
725 self._pid = pid
726 if pid != 0:
727 vms = self.walkdir(PATH_DEBUGFS_KVM)[1]
728 if len(vms) == 0:
729 self.do_read = False
730
731 self.paths = filter(lambda x: "{}-".format(pid) in x, vms)
732
733 else:
734 self.paths = []
735 self.do_read = True
736 self.reset()
737
738 def read(self, reset=0, by_guest=0):
739 """Returns a dict with format:'file name / field -> current value'.
740
741 Parameter 'reset':
742 0 plain read
743 1 reset field counts to 0
744 2 restore the original field counts
745
746 """
747 results = {}
748
749 # If no debugfs filtering support is available, then don't read.
750 if not self.do_read:
751 return results
752
753 paths = self.paths
754 if self._pid == 0:
755 paths = []
756 for entry in os.walk(PATH_DEBUGFS_KVM):
757 for dir in entry[1]:
758 paths.append(dir)
759 for path in paths:
760 for field in self._fields:
761 value = self.read_field(field, path)
762 key = path + field
763 if reset == 1:
764 self._baseline[key] = value
765 if reset == 2:
766 self._baseline[key] = 0
767 if self._baseline.get(key, -1) == -1:
768 self._baseline[key] = value
769 increment = (results.get(field, 0) + value -
770 self._baseline.get(key, 0))
771 if by_guest:
772 pid = key.split('-')[0]
773 if pid in results:
774 results[pid] += increment
775 else:
776 results[pid] = increment
777 else:
778 results[field] = increment
779
780 return results
781
782 def read_field(self, field, path):
783 """Returns the value of a single field from a specific VM."""
784 try:
785 return int(open(os.path.join(PATH_DEBUGFS_KVM,
786 path,
787 field))
788 .read())
789 except IOError:
790 return 0
791
792 def reset(self):
793 """Reset field counters"""
794 self._baseline = {}
795 self.read(1)
796
797 def restore(self):
798 """Reset field counters"""
799 self._baseline = {}
800 self.read(2)
801
802
803 EventStat = namedtuple('EventStat', ['value', 'delta'])
804
805
806 class Stats(object):
807 """Manages the data providers and the data they provide.
808
809 It is used to set filters on the provider's data and collect all
810 provider data.
811
812 """
813 def __init__(self, options):
814 self.providers = self.get_providers(options)
815 self._pid_filter = options.pid
816 self._fields_filter = options.fields
817 self.values = {}
818
819 @staticmethod
820 def get_providers(options):
821 """Returns a list of data providers depending on the passed options."""
822 providers = []
823
824 if options.debugfs:
825 providers.append(DebugfsProvider(options.pid, options.fields,
826 options.dbgfs_include_past))
827 if options.tracepoints or not providers:
828 providers.append(TracepointProvider(options.pid, options.fields))
829
830 return providers
831
832 def update_provider_filters(self):
833 """Propagates fields filters to providers."""
834 # As we reset the counters when updating the fields we can
835 # also clear the cache of old values.
836 self.values = {}
837 for provider in self.providers:
838 provider.update_fields(self._fields_filter)
839
840 def reset(self):
841 self.values = {}
842 for provider in self.providers:
843 provider.reset()
844
845 @property
846 def fields_filter(self):
847 return self._fields_filter
848
849 @fields_filter.setter
850 def fields_filter(self, fields_filter):
851 if fields_filter != self._fields_filter:
852 self._fields_filter = fields_filter
853 self.update_provider_filters()
854
855 @property
856 def pid_filter(self):
857 return self._pid_filter
858
859 @pid_filter.setter
860 def pid_filter(self, pid):
861 if pid != self._pid_filter:
862 self._pid_filter = pid
863 self.values = {}
864 for provider in self.providers:
865 provider.pid = self._pid_filter
866
867 def get(self, by_guest=0):
868 """Returns a dict with field -> (value, delta to last value) of all
869 provider data."""
870 for provider in self.providers:
871 new = provider.read(by_guest=by_guest)
872 for key in new if by_guest else provider.fields:
873 oldval = self.values.get(key, EventStat(0, 0)).value
874 newval = new.get(key, 0)
875 newdelta = newval - oldval
876 self.values[key] = EventStat(newval, newdelta)
877 return self.values
878
879 def toggle_display_guests(self, to_pid):
880 """Toggle between collection of stats by individual event and by
881 guest pid
882
883 Events reported by DebugfsProvider change when switching to/from
884 reading by guest values. Hence we have to remove the excess event
885 names from self.values.
886
887 """
888 if any(isinstance(ins, TracepointProvider) for ins in self.providers):
889 return 1
890 if to_pid:
891 for provider in self.providers:
892 if isinstance(provider, DebugfsProvider):
893 for key in provider.fields:
894 if key in self.values.keys():
895 del self.values[key]
896 else:
897 oldvals = self.values.copy()
898 for key in oldvals:
899 if key.isdigit():
900 del self.values[key]
901 # Update oldval (see get())
902 self.get(to_pid)
903 return 0
904
905 DELAY_DEFAULT = 3.0
906 MAX_GUEST_NAME_LEN = 48
907 MAX_REGEX_LEN = 44
908 DEFAULT_REGEX = r'^[^\(]*$'
909 SORT_DEFAULT = 0
910
911
912 class Tui(object):
913 """Instruments curses to draw a nice text ui."""
914 def __init__(self, stats):
915 self.stats = stats
916 self.screen = None
917 self._delay_initial = 0.25
918 self._delay_regular = DELAY_DEFAULT
919 self._sorting = SORT_DEFAULT
920 self._display_guests = 0
921
922 def __enter__(self):
923 """Initialises curses for later use. Based on curses.wrapper
924 implementation from the Python standard library."""
925 self.screen = curses.initscr()
926 curses.noecho()
927 curses.cbreak()
928
929 # The try/catch works around a minor bit of
930 # over-conscientiousness in the curses module, the error
931 # return from C start_color() is ignorable.
932 try:
933 curses.start_color()
934 except curses.error:
935 pass
936
937 # Hide cursor in extra statement as some monochrome terminals
938 # might support hiding but not colors.
939 try:
940 curses.curs_set(0)
941 except curses.error:
942 pass
943
944 curses.use_default_colors()
945 return self
946
947 def __exit__(self, *exception):
948 """Resets the terminal to its normal state. Based on curses.wrapper
949 implementation from the Python standard library."""
950 if self.screen:
951 self.screen.keypad(0)
952 curses.echo()
953 curses.nocbreak()
954 curses.endwin()
955
956 @staticmethod
957 def get_all_gnames():
958 """Returns a list of (pid, gname) tuples of all running guests"""
959 res = []
960 try:
961 child = subprocess.Popen(['ps', '-A', '--format', 'pid,args'],
962 stdout=subprocess.PIPE)
963 except:
964 raise Exception
965 for line in child.stdout:
966 line = line.decode(ENCODING).lstrip().split(' ', 1)
967 # perform a sanity check before calling the more expensive
968 # function to possibly extract the guest name
969 if ' -name ' in line[1]:
970 res.append((line[0], Tui.get_gname_from_pid(line[0])))
971 child.stdout.close()
972
973 return res
974
975 def print_all_gnames(self, row):
976 """Print a list of all running guests along with their pids."""
977 self.screen.addstr(row, 2, '%8s %-60s' %
978 ('Pid', 'Guest Name (fuzzy list, might be '
979 'inaccurate!)'),
980 curses.A_UNDERLINE)
981 row += 1
982 try:
983 for line in self.get_all_gnames():
984 self.screen.addstr(row, 2, '%8s %-60s' % (line[0], line[1]))
985 row += 1
986 if row >= self.screen.getmaxyx()[0]:
987 break
988 except Exception:
989 self.screen.addstr(row + 1, 2, 'Not available')
990
991 @staticmethod
992 def get_pid_from_gname(gname):
993 """Fuzzy function to convert guest name to QEMU process pid.
994
995 Returns a list of potential pids, can be empty if no match found.
996 Throws an exception on processing errors.
997
998 """
999 pids = []
1000 for line in Tui.get_all_gnames():
1001 if gname == line[1]:
1002 pids.append(int(line[0]))
1003
1004 return pids
1005
1006 @staticmethod
1007 def get_gname_from_pid(pid):
1008 """Returns the guest name for a QEMU process pid.
1009
1010 Extracts the guest name from the QEMU comma line by processing the
1011 '-name' option. Will also handle names specified out of sequence.
1012
1013 """
1014 name = ''
1015 try:
1016 line = open('/proc/{}/cmdline'
1017 .format(pid), 'r').read().split('\0')
1018 parms = line[line.index('-name') + 1].split(',')
1019 while '' in parms:
1020 # commas are escaped (i.e. ',,'), hence e.g. 'foo,bar' results
1021 # in # ['foo', '', 'bar'], which we revert here
1022 idx = parms.index('')
1023 parms[idx - 1] += ',' + parms[idx + 1]
1024 del parms[idx:idx+2]
1025 # the '-name' switch allows for two ways to specify the guest name,
1026 # where the plain name overrides the name specified via 'guest='
1027 for arg in parms:
1028 if '=' not in arg:
1029 name = arg
1030 break
1031 if arg[:6] == 'guest=':
1032 name = arg[6:]
1033 except (ValueError, IOError, IndexError):
1034 pass
1035
1036 return name
1037
1038 def update_drilldown(self):
1039 """Sets or removes a filter that only allows fields without braces."""
1040 if not self.stats.fields_filter:
1041 self.stats.fields_filter = DEFAULT_REGEX
1042
1043 elif self.stats.fields_filter == DEFAULT_REGEX:
1044 self.stats.fields_filter = None
1045
1046 def update_pid(self, pid):
1047 """Propagates pid selection to stats object."""
1048 self.stats.pid_filter = pid
1049
1050 def refresh_header(self, pid=None):
1051 """Refreshes the header."""
1052 if pid is None:
1053 pid = self.stats.pid_filter
1054 self.screen.erase()
1055 gname = self.get_gname_from_pid(pid)
1056 if gname:
1057 gname = ('({})'.format(gname[:MAX_GUEST_NAME_LEN] + '...'
1058 if len(gname) > MAX_GUEST_NAME_LEN
1059 else gname))
1060 if pid > 0:
1061 self.screen.addstr(0, 0, 'kvm statistics - pid {0} {1}'
1062 .format(pid, gname), curses.A_BOLD)
1063 else:
1064 self.screen.addstr(0, 0, 'kvm statistics - summary', curses.A_BOLD)
1065 if self.stats.fields_filter and self.stats.fields_filter \
1066 != DEFAULT_REGEX:
1067 regex = self.stats.fields_filter
1068 if len(regex) > MAX_REGEX_LEN:
1069 regex = regex[:MAX_REGEX_LEN] + '...'
1070 self.screen.addstr(1, 17, 'regex filter: {0}'.format(regex))
1071 if self._display_guests:
1072 col_name = 'Guest Name'
1073 else:
1074 col_name = 'Event'
1075 self.screen.addstr(2, 1, '%-40s %10s%7s %8s' %
1076 (col_name, 'Total', '%Total', 'CurAvg/s'),
1077 curses.A_STANDOUT)
1078 self.screen.addstr(4, 1, 'Collecting data...')
1079 self.screen.refresh()
1080
1081 def refresh_body(self, sleeptime):
1082 row = 3
1083 self.screen.move(row, 0)
1084 self.screen.clrtobot()
1085 stats = self.stats.get(self._display_guests)
1086 total = 0.
1087 ctotal = 0.
1088 for key, values in stats.items():
1089 if key.find('(') == -1:
1090 total += values.value
1091 else:
1092 ctotal += values.value
1093 if total == 0.:
1094 # we don't have any fields, or all non-child events are filtered
1095 total = ctotal
1096
1097 if self._sorting == SORT_DEFAULT:
1098 def sortkey((_k, v)):
1099 # sort by (delta value, overall value)
1100 return (v.delta, v.value)
1101 else:
1102 def sortkey((_k, v)):
1103 # sort by overall value
1104 return v.value
1105
1106 tavg = 0
1107 for key, values in sorted(stats.items(), key=sortkey, reverse=True):
1108 if row >= self.screen.getmaxyx()[0] - 1:
1109 break
1110 if not values.value and not values.delta:
1111 break
1112 if values.value is not None:
1113 cur = int(round(values.delta / sleeptime)) if values.delta else ''
1114 if self._display_guests:
1115 key = self.get_gname_from_pid(key)
1116 self.screen.addstr(row, 1, '%-40s %10d%7.1f %8s' %
1117 (key, values.value, values.value * 100 / total,
1118 cur))
1119 if cur != '' and key.find('(') == -1:
1120 tavg += cur
1121 row += 1
1122 if row == 3:
1123 self.screen.addstr(4, 1, 'No matching events reported yet')
1124 else:
1125 self.screen.addstr(row, 1, '%-40s %10d %8s' %
1126 ('Total', total, tavg if tavg else ''),
1127 curses.A_BOLD)
1128 self.screen.refresh()
1129
1130 def show_msg(self, text):
1131 """Display message centered text and exit on key press"""
1132 hint = 'Press any key to continue'
1133 curses.cbreak()
1134 self.screen.erase()
1135 (x, term_width) = self.screen.getmaxyx()
1136 row = 2
1137 for line in text:
1138 start = (term_width - len(line)) / 2
1139 self.screen.addstr(row, start, line)
1140 row += 1
1141 self.screen.addstr(row + 1, (term_width - len(hint)) / 2, hint,
1142 curses.A_STANDOUT)
1143 self.screen.getkey()
1144
1145 def show_help_interactive(self):
1146 """Display help with list of interactive commands"""
1147 msg = (' b toggle events by guests (debugfs only, honors'
1148 ' filters)',
1149 ' c clear filter',
1150 ' f filter by regular expression',
1151 ' g filter by guest name',
1152 ' h display interactive commands reference',
1153 ' o toggle sorting order (Total vs CurAvg/s)',
1154 ' p filter by PID',
1155 ' q quit',
1156 ' r reset stats',
1157 ' s set update interval',
1158 ' x toggle reporting of stats for individual child trace'
1159 ' events',
1160 'Any other key refreshes statistics immediately')
1161 curses.cbreak()
1162 self.screen.erase()
1163 self.screen.addstr(0, 0, "Interactive commands reference",
1164 curses.A_BOLD)
1165 self.screen.addstr(2, 0, "Press any key to exit", curses.A_STANDOUT)
1166 row = 4
1167 for line in msg:
1168 self.screen.addstr(row, 0, line)
1169 row += 1
1170 self.screen.getkey()
1171 self.refresh_header()
1172
1173 def show_filter_selection(self):
1174 """Draws filter selection mask.
1175
1176 Asks for a valid regex and sets the fields filter accordingly.
1177
1178 """
1179 while True:
1180 self.screen.erase()
1181 self.screen.addstr(0, 0,
1182 "Show statistics for events matching a regex.",
1183 curses.A_BOLD)
1184 self.screen.addstr(2, 0,
1185 "Current regex: {0}"
1186 .format(self.stats.fields_filter))
1187 self.screen.addstr(3, 0, "New regex: ")
1188 curses.echo()
1189 regex = self.screen.getstr().decode(ENCODING)
1190 curses.noecho()
1191 if len(regex) == 0:
1192 self.stats.fields_filter = DEFAULT_REGEX
1193 self.refresh_header()
1194 return
1195 try:
1196 re.compile(regex)
1197 self.stats.fields_filter = regex
1198 self.refresh_header()
1199 return
1200 except re.error:
1201 continue
1202
1203 def show_vm_selection_by_pid(self):
1204 """Draws PID selection mask.
1205
1206 Asks for a pid until a valid pid or 0 has been entered.
1207
1208 """
1209 msg = ''
1210 while True:
1211 self.screen.erase()
1212 self.screen.addstr(0, 0,
1213 'Show statistics for specific pid.',
1214 curses.A_BOLD)
1215 self.screen.addstr(1, 0,
1216 'This might limit the shown data to the trace '
1217 'statistics.')
1218 self.screen.addstr(5, 0, msg)
1219 self.print_all_gnames(7)
1220
1221 curses.echo()
1222 self.screen.addstr(3, 0, "Pid [0 or pid]: ")
1223 pid = self.screen.getstr().decode(ENCODING)
1224 curses.noecho()
1225
1226 try:
1227 if len(pid) > 0:
1228 pid = int(pid)
1229 if pid != 0 and not os.path.isdir(os.path.join('/proc/',
1230 str(pid))):
1231 msg = '"' + str(pid) + '": Not a running process'
1232 continue
1233 else:
1234 pid = 0
1235 self.refresh_header(pid)
1236 self.update_pid(pid)
1237 break
1238 except ValueError:
1239 msg = '"' + str(pid) + '": Not a valid pid'
1240
1241 def show_set_update_interval(self):
1242 """Draws update interval selection mask."""
1243 msg = ''
1244 while True:
1245 self.screen.erase()
1246 self.screen.addstr(0, 0, 'Set update interval (defaults to %fs).' %
1247 DELAY_DEFAULT, curses.A_BOLD)
1248 self.screen.addstr(4, 0, msg)
1249 self.screen.addstr(2, 0, 'Change delay from %.1fs to ' %
1250 self._delay_regular)
1251 curses.echo()
1252 val = self.screen.getstr().decode(ENCODING)
1253 curses.noecho()
1254
1255 try:
1256 if len(val) > 0:
1257 delay = float(val)
1258 if delay < 0.1:
1259 msg = '"' + str(val) + '": Value must be >=0.1'
1260 continue
1261 if delay > 25.5:
1262 msg = '"' + str(val) + '": Value must be <=25.5'
1263 continue
1264 else:
1265 delay = DELAY_DEFAULT
1266 self._delay_regular = delay
1267 break
1268
1269 except ValueError:
1270 msg = '"' + str(val) + '": Invalid value'
1271 self.refresh_header()
1272
1273 def show_vm_selection_by_guest_name(self):
1274 """Draws guest selection mask.
1275
1276 Asks for a guest name until a valid guest name or '' is entered.
1277
1278 """
1279 msg = ''
1280 while True:
1281 self.screen.erase()
1282 self.screen.addstr(0, 0,
1283 'Show statistics for specific guest.',
1284 curses.A_BOLD)
1285 self.screen.addstr(1, 0,
1286 'This might limit the shown data to the trace '
1287 'statistics.')
1288 self.screen.addstr(5, 0, msg)
1289 self.print_all_gnames(7)
1290 curses.echo()
1291 self.screen.addstr(3, 0, "Guest [ENTER or guest]: ")
1292 gname = self.screen.getstr().decode(ENCODING)
1293 curses.noecho()
1294
1295 if not gname:
1296 self.refresh_header(0)
1297 self.update_pid(0)
1298 break
1299 else:
1300 pids = []
1301 try:
1302 pids = self.get_pid_from_gname(gname)
1303 except:
1304 msg = '"' + gname + '": Internal error while searching, ' \
1305 'use pid filter instead'
1306 continue
1307 if len(pids) == 0:
1308 msg = '"' + gname + '": Not an active guest'
1309 continue
1310 if len(pids) > 1:
1311 msg = '"' + gname + '": Multiple matches found, use pid ' \
1312 'filter instead'
1313 continue
1314 self.refresh_header(pids[0])
1315 self.update_pid(pids[0])
1316 break
1317
1318 def show_stats(self):
1319 """Refreshes the screen and processes user input."""
1320 sleeptime = self._delay_initial
1321 self.refresh_header()
1322 start = 0.0 # result based on init value never appears on screen
1323 while True:
1324 self.refresh_body(time.time() - start)
1325 curses.halfdelay(int(sleeptime * 10))
1326 start = time.time()
1327 sleeptime = self._delay_regular
1328 try:
1329 char = self.screen.getkey()
1330 if char == 'b':
1331 self._display_guests = not self._display_guests
1332 if self.stats.toggle_display_guests(self._display_guests):
1333 self.show_msg(['Command not available with tracepoints'
1334 ' enabled', 'Restart with debugfs only '
1335 '(see option \'-d\') and try again!'])
1336 self._display_guests = not self._display_guests
1337 self.refresh_header()
1338 if char == 'c':
1339 self.stats.fields_filter = DEFAULT_REGEX
1340 self.refresh_header(0)
1341 self.update_pid(0)
1342 if char == 'f':
1343 curses.curs_set(1)
1344 self.show_filter_selection()
1345 curses.curs_set(0)
1346 sleeptime = self._delay_initial
1347 if char == 'g':
1348 curses.curs_set(1)
1349 self.show_vm_selection_by_guest_name()
1350 curses.curs_set(0)
1351 sleeptime = self._delay_initial
1352 if char == 'h':
1353 self.show_help_interactive()
1354 if char == 'o':
1355 self._sorting = not self._sorting
1356 if char == 'p':
1357 curses.curs_set(1)
1358 self.show_vm_selection_by_pid()
1359 curses.curs_set(0)
1360 sleeptime = self._delay_initial
1361 if char == 'q':
1362 break
1363 if char == 'r':
1364 self.stats.reset()
1365 if char == 's':
1366 curses.curs_set(1)
1367 self.show_set_update_interval()
1368 curses.curs_set(0)
1369 sleeptime = self._delay_initial
1370 if char == 'x':
1371 self.update_drilldown()
1372 # prevents display of current values on next refresh
1373 self.stats.get(self._display_guests)
1374 except KeyboardInterrupt:
1375 break
1376 except curses.error:
1377 continue
1378
1379
1380 def batch(stats):
1381 """Prints statistics in a key, value format."""
1382 try:
1383 s = stats.get()
1384 time.sleep(1)
1385 s = stats.get()
1386 for key, values in sorted(s.items()):
1387 print('%-42s%10d%10d' % (key, values.value, values.delta))
1388 except KeyboardInterrupt:
1389 pass
1390
1391
1392 def log(stats):
1393 """Prints statistics as reiterating key block, multiple value blocks."""
1394 keys = sorted(stats.get().keys())
1395
1396 def banner():
1397 for key in keys:
1398 print(key, end=' ')
1399 print()
1400
1401 def statline():
1402 s = stats.get()
1403 for key in keys:
1404 print(' %9d' % s[key].delta, end=' ')
1405 print()
1406 line = 0
1407 banner_repeat = 20
1408 while True:
1409 try:
1410 time.sleep(1)
1411 if line % banner_repeat == 0:
1412 banner()
1413 statline()
1414 line += 1
1415 except KeyboardInterrupt:
1416 break
1417
1418
1419 def get_options():
1420 """Returns processed program arguments."""
1421 description_text = """
1422 This script displays various statistics about VMs running under KVM.
1423 The statistics are gathered from the KVM debugfs entries and / or the
1424 currently available perf traces.
1425
1426 The monitoring takes additional cpu cycles and might affect the VM's
1427 performance.
1428
1429 Requirements:
1430 - Access to:
1431 %s
1432 %s/events/*
1433 /proc/pid/task
1434 - /proc/sys/kernel/perf_event_paranoid < 1 if user has no
1435 CAP_SYS_ADMIN and perf events are used.
1436 - CAP_SYS_RESOURCE if the hard limit is not high enough to allow
1437 the large number of files that are possibly opened.
1438
1439 Interactive Commands:
1440 b toggle events by guests (debugfs only, honors filters)
1441 c clear filter
1442 f filter by regular expression
1443 g filter by guest name
1444 h display interactive commands reference
1445 o toggle sorting order (Total vs CurAvg/s)
1446 p filter by PID
1447 q quit
1448 r reset stats
1449 s set update interval
1450 x toggle reporting of stats for individual child trace events
1451 Press any other key to refresh statistics immediately.
1452 """ % (PATH_DEBUGFS_KVM, PATH_DEBUGFS_TRACING)
1453
1454 class PlainHelpFormatter(optparse.IndentedHelpFormatter):
1455 def format_description(self, description):
1456 if description:
1457 return description + "\n"
1458 else:
1459 return ""
1460
1461 def cb_guest_to_pid(option, opt, val, parser):
1462 try:
1463 pids = Tui.get_pid_from_gname(val)
1464 except:
1465 sys.exit('Error while searching for guest "{}". Use "-p" to '
1466 'specify a pid instead?'.format(val))
1467 if len(pids) == 0:
1468 sys.exit('Error: No guest by the name "{}" found'.format(val))
1469 if len(pids) > 1:
1470 sys.exit('Error: Multiple processes found (pids: {}). Use "-p" '
1471 'to specify the desired pid'.format(" ".join(pids)))
1472 parser.values.pid = pids[0]
1473
1474 optparser = optparse.OptionParser(description=description_text,
1475 formatter=PlainHelpFormatter())
1476 optparser.add_option('-1', '--once', '--batch',
1477 action='store_true',
1478 default=False,
1479 dest='once',
1480 help='run in batch mode for one second',
1481 )
1482 optparser.add_option('-i', '--debugfs-include-past',
1483 action='store_true',
1484 default=False,
1485 dest='dbgfs_include_past',
1486 help='include all available data on past events for '
1487 'debugfs',
1488 )
1489 optparser.add_option('-l', '--log',
1490 action='store_true',
1491 default=False,
1492 dest='log',
1493 help='run in logging mode (like vmstat)',
1494 )
1495 optparser.add_option('-t', '--tracepoints',
1496 action='store_true',
1497 default=False,
1498 dest='tracepoints',
1499 help='retrieve statistics from tracepoints',
1500 )
1501 optparser.add_option('-d', '--debugfs',
1502 action='store_true',
1503 default=False,
1504 dest='debugfs',
1505 help='retrieve statistics from debugfs',
1506 )
1507 optparser.add_option('-f', '--fields',
1508 action='store',
1509 default=DEFAULT_REGEX,
1510 dest='fields',
1511 help='''fields to display (regex)
1512 "-f help" for a list of available events''',
1513 )
1514 optparser.add_option('-p', '--pid',
1515 action='store',
1516 default=0,
1517 type='int',
1518 dest='pid',
1519 help='restrict statistics to pid',
1520 )
1521 optparser.add_option('-g', '--guest',
1522 action='callback',
1523 type='string',
1524 dest='pid',
1525 metavar='GUEST',
1526 help='restrict statistics to guest by name',
1527 callback=cb_guest_to_pid,
1528 )
1529 options, unkn = optparser.parse_args(sys.argv)
1530 if len(unkn) != 1:
1531 sys.exit('Error: Extra argument(s): ' + ' '.join(unkn[1:]))
1532 try:
1533 # verify that we were passed a valid regex up front
1534 re.compile(options.fields)
1535 except re.error:
1536 sys.exit('Error: "' + options.fields + '" is not a valid regular '
1537 'expression')
1538
1539 return options
1540
1541
1542 def check_access(options):
1543 """Exits if the current user can't access all needed directories."""
1544 if not os.path.exists('/sys/kernel/debug'):
1545 sys.stderr.write('Please enable CONFIG_DEBUG_FS in your kernel.')
1546 sys.exit(1)
1547
1548 if not os.path.exists(PATH_DEBUGFS_KVM):
1549 sys.stderr.write("Please make sure, that debugfs is mounted and "
1550 "readable by the current user:\n"
1551 "('mount -t debugfs debugfs /sys/kernel/debug')\n"
1552 "Also ensure, that the kvm modules are loaded.\n")
1553 sys.exit(1)
1554
1555 if not os.path.exists(PATH_DEBUGFS_TRACING) and (options.tracepoints or
1556 not options.debugfs):
1557 sys.stderr.write("Please enable CONFIG_TRACING in your kernel "
1558 "when using the option -t (default).\n"
1559 "If it is enabled, make {0} readable by the "
1560 "current user.\n"
1561 .format(PATH_DEBUGFS_TRACING))
1562 if options.tracepoints:
1563 sys.exit(1)
1564
1565 sys.stderr.write("Falling back to debugfs statistics!\n")
1566 options.debugfs = True
1567 time.sleep(5)
1568
1569 return options
1570
1571
1572 def main():
1573 options = get_options()
1574 options = check_access(options)
1575
1576 if (options.pid > 0 and
1577 not os.path.isdir(os.path.join('/proc/',
1578 str(options.pid)))):
1579 sys.stderr.write('Did you use a (unsupported) tid instead of a pid?\n')
1580 sys.exit('Specified pid does not exist.')
1581
1582 stats = Stats(options)
1583
1584 if options.fields == 'help':
1585 stats.fields_filter = None
1586 event_list = []
1587 for key in stats.get().keys():
1588 event_list.append(key.split('(', 1)[0])
1589 sys.stdout.write(' ' + '\n '.join(sorted(set(event_list))) + '\n')
1590 sys.exit(0)
1591
1592 if options.log:
1593 log(stats)
1594 elif not options.once:
1595 with Tui(stats) as tui:
1596 tui.show_stats()
1597 else:
1598 batch(stats)
1599
1600 if __name__ == "__main__":
1601 main()