]> git.ipfire.org Git - thirdparty/kernel/stable.git/blob - tools/kvm/kvm_stat/kvm_stat
bc508dae286c8bfb75d0c2d9165b073d1f0ed178
[thirdparty/kernel/stable.git] / tools / kvm / kvm_stat / kvm_stat
1 #!/usr/bin/env python3
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)]
579 # add parents for child fields - otherwise we won't see any output!
580 for field in self._fields:
581 parent = ARCH.tracepoint_is_child(field)
582 if (parent and parent not in self._fields):
583 self.fields.append(parent)
584
585 @staticmethod
586 def _get_online_cpus():
587 """Returns a list of cpu id integers."""
588 def parse_int_list(list_string):
589 """Returns an int list from a string of comma separated integers and
590 integer ranges."""
591 integers = []
592 members = list_string.split(',')
593
594 for member in members:
595 if '-' not in member:
596 integers.append(int(member))
597 else:
598 int_range = member.split('-')
599 integers.extend(range(int(int_range[0]),
600 int(int_range[1]) + 1))
601
602 return integers
603
604 with open('/sys/devices/system/cpu/online') as cpu_list:
605 cpu_string = cpu_list.readline()
606 return parse_int_list(cpu_string)
607
608 def _setup_traces(self):
609 """Creates all event and group objects needed to be able to retrieve
610 data."""
611 fields = self._get_available_fields()
612 if self._pid > 0:
613 # Fetch list of all threads of the monitored pid, as qemu
614 # starts a thread for each vcpu.
615 path = os.path.join('/proc', str(self._pid), 'task')
616 groupids = self.walkdir(path)[1]
617 else:
618 groupids = self._get_online_cpus()
619
620 # The constant is needed as a buffer for python libs, std
621 # streams and other files that the script opens.
622 newlim = len(groupids) * len(fields) + 50
623 try:
624 softlim_, hardlim = resource.getrlimit(resource.RLIMIT_NOFILE)
625
626 if hardlim < newlim:
627 # Now we need CAP_SYS_RESOURCE, to increase the hard limit.
628 resource.setrlimit(resource.RLIMIT_NOFILE, (newlim, newlim))
629 else:
630 # Raising the soft limit is sufficient.
631 resource.setrlimit(resource.RLIMIT_NOFILE, (newlim, hardlim))
632
633 except ValueError:
634 sys.exit("NOFILE rlimit could not be raised to {0}".format(newlim))
635
636 for groupid in groupids:
637 group = Group()
638 for name in fields:
639 tracepoint = name
640 tracefilter = None
641 match = re.match(r'(.*)\((.*)\)', name)
642 if match:
643 tracepoint, sub = match.groups()
644 tracefilter = ('%s==%d\0' %
645 (self.filters[tracepoint][0],
646 self.filters[tracepoint][1][sub]))
647
648 # From perf_event_open(2):
649 # pid > 0 and cpu == -1
650 # This measures the specified process/thread on any CPU.
651 #
652 # pid == -1 and cpu >= 0
653 # This measures all processes/threads on the specified CPU.
654 trace_cpu = groupid if self._pid == 0 else -1
655 trace_pid = int(groupid) if self._pid != 0 else -1
656
657 group.add_event(Event(name=name,
658 group=group,
659 trace_cpu=trace_cpu,
660 trace_pid=trace_pid,
661 trace_point=tracepoint,
662 trace_filter=tracefilter))
663
664 self.group_leaders.append(group)
665
666 @property
667 def fields(self):
668 return self._fields
669
670 @fields.setter
671 def fields(self, fields):
672 """Enables/disables the (un)wanted events"""
673 self._fields = fields
674 for group in self.group_leaders:
675 for index, event in enumerate(group.events):
676 if event.name in fields:
677 event.reset()
678 event.enable()
679 else:
680 # Do not disable the group leader.
681 # It would disable all of its events.
682 if index != 0:
683 event.disable()
684
685 @property
686 def pid(self):
687 return self._pid
688
689 @pid.setter
690 def pid(self, pid):
691 """Changes the monitored pid by setting new traces."""
692 self._pid = pid
693 # The garbage collector will get rid of all Event/Group
694 # objects and open files after removing the references.
695 self.group_leaders = []
696 self._setup_traces()
697 self.fields = self._fields
698
699 def read(self, by_guest=0):
700 """Returns 'event name: current value' for all enabled events."""
701 ret = defaultdict(int)
702 for group in self.group_leaders:
703 for name, val in group.read().items():
704 if name not in self._fields:
705 continue
706 parent = ARCH.tracepoint_is_child(name)
707 if parent:
708 name += ' ' + parent
709 ret[name] += val
710 return ret
711
712 def reset(self):
713 """Reset all field counters"""
714 for group in self.group_leaders:
715 for event in group.events:
716 event.reset()
717
718
719 class DebugfsProvider(Provider):
720 """Provides data from the files that KVM creates in the kvm debugfs
721 folder."""
722 def __init__(self, pid, fields_filter, include_past):
723 self.update_fields(fields_filter)
724 self._baseline = {}
725 self.do_read = True
726 self.paths = []
727 super(DebugfsProvider, self).__init__(pid)
728 if include_past:
729 self._restore()
730
731 def _get_available_fields(self):
732 """"Returns a list of available fields.
733
734 The fields are all available KVM debugfs files
735
736 """
737 return self.walkdir(PATH_DEBUGFS_KVM)[2]
738
739 def update_fields(self, fields_filter):
740 """Refresh fields, applying fields_filter"""
741 self._fields = [field for field in self._get_available_fields()
742 if self.is_field_wanted(fields_filter, field)]
743 # add parents for child fields - otherwise we won't see any output!
744 for field in self._fields:
745 parent = ARCH.debugfs_is_child(field)
746 if (parent and parent not in self._fields):
747 self.fields.append(parent)
748
749 @property
750 def fields(self):
751 return self._fields
752
753 @fields.setter
754 def fields(self, fields):
755 self._fields = fields
756 self.reset()
757
758 @property
759 def pid(self):
760 return self._pid
761
762 @pid.setter
763 def pid(self, pid):
764 self._pid = pid
765 if pid != 0:
766 vms = self.walkdir(PATH_DEBUGFS_KVM)[1]
767 if len(vms) == 0:
768 self.do_read = False
769
770 self.paths = list(filter(lambda x: "{}-".format(pid) in x, vms))
771
772 else:
773 self.paths = []
774 self.do_read = True
775
776 def _verify_paths(self):
777 """Remove invalid paths"""
778 for path in self.paths:
779 if not os.path.exists(os.path.join(PATH_DEBUGFS_KVM, path)):
780 self.paths.remove(path)
781 continue
782
783 def read(self, reset=0, by_guest=0):
784 """Returns a dict with format:'file name / field -> current value'.
785
786 Parameter 'reset':
787 0 plain read
788 1 reset field counts to 0
789 2 restore the original field counts
790
791 """
792 results = {}
793
794 # If no debugfs filtering support is available, then don't read.
795 if not self.do_read:
796 return results
797 self._verify_paths()
798
799 paths = self.paths
800 if self._pid == 0:
801 paths = []
802 for entry in os.walk(PATH_DEBUGFS_KVM):
803 for dir in entry[1]:
804 paths.append(dir)
805 for path in paths:
806 for field in self._fields:
807 value = self._read_field(field, path)
808 key = path + field
809 if reset == 1:
810 self._baseline[key] = value
811 if reset == 2:
812 self._baseline[key] = 0
813 if self._baseline.get(key, -1) == -1:
814 self._baseline[key] = value
815 parent = ARCH.debugfs_is_child(field)
816 if parent:
817 field = field + ' ' + parent
818 else:
819 if by_guest:
820 field = key.split('-')[0] # set 'field' to 'pid'
821 increment = value - self._baseline.get(key, 0)
822 if field in results:
823 results[field] += increment
824 else:
825 results[field] = increment
826
827 return results
828
829 def _read_field(self, field, path):
830 """Returns the value of a single field from a specific VM."""
831 try:
832 return int(open(os.path.join(PATH_DEBUGFS_KVM,
833 path,
834 field))
835 .read())
836 except IOError:
837 return 0
838
839 def reset(self):
840 """Reset field counters"""
841 self._baseline = {}
842 self.read(1)
843
844 def _restore(self):
845 """Reset field counters"""
846 self._baseline = {}
847 self.read(2)
848
849
850 EventStat = namedtuple('EventStat', ['value', 'delta'])
851
852
853 class Stats(object):
854 """Manages the data providers and the data they provide.
855
856 It is used to set filters on the provider's data and collect all
857 provider data.
858
859 """
860 def __init__(self, options):
861 self.providers = self._get_providers(options)
862 self._pid_filter = options.pid
863 self._fields_filter = options.fields
864 self.values = {}
865 self._child_events = False
866
867 def _get_providers(self, options):
868 """Returns a list of data providers depending on the passed options."""
869 providers = []
870
871 if options.debugfs:
872 providers.append(DebugfsProvider(options.pid, options.fields,
873 options.dbgfs_include_past))
874 if options.tracepoints or not providers:
875 providers.append(TracepointProvider(options.pid, options.fields))
876
877 return providers
878
879 def _update_provider_filters(self):
880 """Propagates fields filters to providers."""
881 # As we reset the counters when updating the fields we can
882 # also clear the cache of old values.
883 self.values = {}
884 for provider in self.providers:
885 provider.update_fields(self._fields_filter)
886
887 def reset(self):
888 self.values = {}
889 for provider in self.providers:
890 provider.reset()
891
892 @property
893 def fields_filter(self):
894 return self._fields_filter
895
896 @fields_filter.setter
897 def fields_filter(self, fields_filter):
898 if fields_filter != self._fields_filter:
899 self._fields_filter = fields_filter
900 self._update_provider_filters()
901
902 @property
903 def pid_filter(self):
904 return self._pid_filter
905
906 @pid_filter.setter
907 def pid_filter(self, pid):
908 if pid != self._pid_filter:
909 self._pid_filter = pid
910 self.values = {}
911 for provider in self.providers:
912 provider.pid = self._pid_filter
913
914 @property
915 def child_events(self):
916 return self._child_events
917
918 @child_events.setter
919 def child_events(self, val):
920 self._child_events = val
921 for provider in self.providers:
922 provider.child_events = val
923
924 def get(self, by_guest=0):
925 """Returns a dict with field -> (value, delta to last value) of all
926 provider data.
927 Key formats:
928 * plain: 'key' is event name
929 * child-parent: 'key' is in format '<child> <parent>'
930 * pid: 'key' is the pid of the guest, and the record contains the
931 aggregated event data
932 These formats are generated by the providers, and handled in class TUI.
933 """
934 for provider in self.providers:
935 new = provider.read(by_guest=by_guest)
936 for key in new:
937 oldval = self.values.get(key, EventStat(0, 0)).value
938 newval = new.get(key, 0)
939 newdelta = newval - oldval
940 self.values[key] = EventStat(newval, newdelta)
941 return self.values
942
943 def toggle_display_guests(self, to_pid):
944 """Toggle between collection of stats by individual event and by
945 guest pid
946
947 Events reported by DebugfsProvider change when switching to/from
948 reading by guest values. Hence we have to remove the excess event
949 names from self.values.
950
951 """
952 if any(isinstance(ins, TracepointProvider) for ins in self.providers):
953 return 1
954 if to_pid:
955 for provider in self.providers:
956 if isinstance(provider, DebugfsProvider):
957 for key in provider.fields:
958 if key in self.values.keys():
959 del self.values[key]
960 else:
961 oldvals = self.values.copy()
962 for key in oldvals:
963 if key.isdigit():
964 del self.values[key]
965 # Update oldval (see get())
966 self.get(to_pid)
967 return 0
968
969
970 DELAY_DEFAULT = 3.0
971 MAX_GUEST_NAME_LEN = 48
972 MAX_REGEX_LEN = 44
973 SORT_DEFAULT = 0
974
975
976 class Tui(object):
977 """Instruments curses to draw a nice text ui."""
978 def __init__(self, stats):
979 self.stats = stats
980 self.screen = None
981 self._delay_initial = 0.25
982 self._delay_regular = DELAY_DEFAULT
983 self._sorting = SORT_DEFAULT
984 self._display_guests = 0
985
986 def __enter__(self):
987 """Initialises curses for later use. Based on curses.wrapper
988 implementation from the Python standard library."""
989 self.screen = curses.initscr()
990 curses.noecho()
991 curses.cbreak()
992
993 # The try/catch works around a minor bit of
994 # over-conscientiousness in the curses module, the error
995 # return from C start_color() is ignorable.
996 try:
997 curses.start_color()
998 except curses.error:
999 pass
1000
1001 # Hide cursor in extra statement as some monochrome terminals
1002 # might support hiding but not colors.
1003 try:
1004 curses.curs_set(0)
1005 except curses.error:
1006 pass
1007
1008 curses.use_default_colors()
1009 return self
1010
1011 def __exit__(self, *exception):
1012 """Resets the terminal to its normal state. Based on curses.wrapper
1013 implementation from the Python standard library."""
1014 if self.screen:
1015 self.screen.keypad(0)
1016 curses.echo()
1017 curses.nocbreak()
1018 curses.endwin()
1019
1020 @staticmethod
1021 def get_all_gnames():
1022 """Returns a list of (pid, gname) tuples of all running guests"""
1023 res = []
1024 try:
1025 child = subprocess.Popen(['ps', '-A', '--format', 'pid,args'],
1026 stdout=subprocess.PIPE)
1027 except:
1028 raise Exception
1029 for line in child.stdout:
1030 line = line.decode(ENCODING).lstrip().split(' ', 1)
1031 # perform a sanity check before calling the more expensive
1032 # function to possibly extract the guest name
1033 if ' -name ' in line[1]:
1034 res.append((line[0], Tui.get_gname_from_pid(line[0])))
1035 child.stdout.close()
1036
1037 return res
1038
1039 def _print_all_gnames(self, row):
1040 """Print a list of all running guests along with their pids."""
1041 self.screen.addstr(row, 2, '%8s %-60s' %
1042 ('Pid', 'Guest Name (fuzzy list, might be '
1043 'inaccurate!)'),
1044 curses.A_UNDERLINE)
1045 row += 1
1046 try:
1047 for line in self.get_all_gnames():
1048 self.screen.addstr(row, 2, '%8s %-60s' % (line[0], line[1]))
1049 row += 1
1050 if row >= self.screen.getmaxyx()[0]:
1051 break
1052 except Exception:
1053 self.screen.addstr(row + 1, 2, 'Not available')
1054
1055 @staticmethod
1056 def get_pid_from_gname(gname):
1057 """Fuzzy function to convert guest name to QEMU process pid.
1058
1059 Returns a list of potential pids, can be empty if no match found.
1060 Throws an exception on processing errors.
1061
1062 """
1063 pids = []
1064 for line in Tui.get_all_gnames():
1065 if gname == line[1]:
1066 pids.append(int(line[0]))
1067
1068 return pids
1069
1070 @staticmethod
1071 def get_gname_from_pid(pid):
1072 """Returns the guest name for a QEMU process pid.
1073
1074 Extracts the guest name from the QEMU comma line by processing the
1075 '-name' option. Will also handle names specified out of sequence.
1076
1077 """
1078 name = ''
1079 try:
1080 line = open('/proc/{}/cmdline'
1081 .format(pid), 'r').read().split('\0')
1082 parms = line[line.index('-name') + 1].split(',')
1083 while '' in parms:
1084 # commas are escaped (i.e. ',,'), hence e.g. 'foo,bar' results
1085 # in # ['foo', '', 'bar'], which we revert here
1086 idx = parms.index('')
1087 parms[idx - 1] += ',' + parms[idx + 1]
1088 del parms[idx:idx+2]
1089 # the '-name' switch allows for two ways to specify the guest name,
1090 # where the plain name overrides the name specified via 'guest='
1091 for arg in parms:
1092 if '=' not in arg:
1093 name = arg
1094 break
1095 if arg[:6] == 'guest=':
1096 name = arg[6:]
1097 except (ValueError, IOError, IndexError):
1098 pass
1099
1100 return name
1101
1102 def _update_pid(self, pid):
1103 """Propagates pid selection to stats object."""
1104 self.screen.addstr(4, 1, 'Updating pid filter...')
1105 self.screen.refresh()
1106 self.stats.pid_filter = pid
1107
1108 def _refresh_header(self, pid=None):
1109 """Refreshes the header."""
1110 if pid is None:
1111 pid = self.stats.pid_filter
1112 self.screen.erase()
1113 gname = self.get_gname_from_pid(pid)
1114 self._gname = gname
1115 if gname:
1116 gname = ('({})'.format(gname[:MAX_GUEST_NAME_LEN] + '...'
1117 if len(gname) > MAX_GUEST_NAME_LEN
1118 else gname))
1119 if pid > 0:
1120 self._headline = 'kvm statistics - pid {0} {1}'.format(pid, gname)
1121 else:
1122 self._headline = 'kvm statistics - summary'
1123 self.screen.addstr(0, 0, self._headline, curses.A_BOLD)
1124 if self.stats.fields_filter:
1125 regex = self.stats.fields_filter
1126 if len(regex) > MAX_REGEX_LEN:
1127 regex = regex[:MAX_REGEX_LEN] + '...'
1128 self.screen.addstr(1, 17, 'regex filter: {0}'.format(regex))
1129 if self._display_guests:
1130 col_name = 'Guest Name'
1131 else:
1132 col_name = 'Event'
1133 self.screen.addstr(2, 1, '%-40s %10s%7s %8s' %
1134 (col_name, 'Total', '%Total', 'CurAvg/s'),
1135 curses.A_STANDOUT)
1136 self.screen.addstr(4, 1, 'Collecting data...')
1137 self.screen.refresh()
1138
1139 def _refresh_body(self, sleeptime):
1140 def insert_child(sorted_items, child, values, parent):
1141 num = len(sorted_items)
1142 for i in range(0, num):
1143 # only add child if parent is present
1144 if parent.startswith(sorted_items[i][0]):
1145 sorted_items.insert(i + 1, (' ' + child, values))
1146
1147 def get_sorted_events(self, stats):
1148 """ separate parent and child events """
1149 if self._sorting == SORT_DEFAULT:
1150 def sortkey(pair):
1151 # sort by (delta value, overall value)
1152 v = pair[1]
1153 return (v.delta, v.value)
1154 else:
1155 def sortkey(pair):
1156 # sort by overall value
1157 v = pair[1]
1158 return v.value
1159
1160 childs = []
1161 sorted_items = []
1162 # we can't rule out child events to appear prior to parents even
1163 # when sorted - separate out all children first, and add in later
1164 for key, values in sorted(stats.items(), key=sortkey,
1165 reverse=True):
1166 if values == (0, 0):
1167 continue
1168 if key.find(' ') != -1:
1169 if not self.stats.child_events:
1170 continue
1171 childs.insert(0, (key, values))
1172 else:
1173 sorted_items.append((key, values))
1174 if self.stats.child_events:
1175 for key, values in childs:
1176 (child, parent) = key.split(' ')
1177 insert_child(sorted_items, child, values, parent)
1178
1179 return sorted_items
1180
1181 if not self._is_running_guest(self.stats.pid_filter):
1182 if self._gname:
1183 try: # ...to identify the guest by name in case it's back
1184 pids = self.get_pid_from_gname(self._gname)
1185 if len(pids) == 1:
1186 self._refresh_header(pids[0])
1187 self._update_pid(pids[0])
1188 return
1189 except:
1190 pass
1191 self._display_guest_dead()
1192 # leave final data on screen
1193 return
1194 row = 3
1195 self.screen.move(row, 0)
1196 self.screen.clrtobot()
1197 stats = self.stats.get(self._display_guests)
1198 total = 0.
1199 ctotal = 0.
1200 for key, values in stats.items():
1201 if self._display_guests:
1202 if self.get_gname_from_pid(key):
1203 total += values.value
1204 continue
1205 if not key.find(' ') != -1:
1206 total += values.value
1207 else:
1208 ctotal += values.value
1209 if total == 0.:
1210 # we don't have any fields, or all non-child events are filtered
1211 total = ctotal
1212
1213 # print events
1214 tavg = 0
1215 tcur = 0
1216 guest_removed = False
1217 for key, values in get_sorted_events(self, stats):
1218 if row >= self.screen.getmaxyx()[0] - 1 or values == (0, 0):
1219 break
1220 if self._display_guests:
1221 key = self.get_gname_from_pid(key)
1222 if not key:
1223 continue
1224 cur = int(round(values.delta / sleeptime)) if values.delta else 0
1225 if cur < 0:
1226 guest_removed = True
1227 continue
1228 if key[0] != ' ':
1229 if values.delta:
1230 tcur += values.delta
1231 ptotal = values.value
1232 ltotal = total
1233 else:
1234 ltotal = ptotal
1235 self.screen.addstr(row, 1, '%-40s %10d%7.1f %8s' % (key,
1236 values.value,
1237 values.value * 100 / float(ltotal), cur))
1238 row += 1
1239 if row == 3:
1240 if guest_removed:
1241 self.screen.addstr(4, 1, 'Guest removed, updating...')
1242 else:
1243 self.screen.addstr(4, 1, 'No matching events reported yet')
1244 if row > 4:
1245 tavg = int(round(tcur / sleeptime)) if tcur > 0 else ''
1246 self.screen.addstr(row, 1, '%-40s %10d %8s' %
1247 ('Total', total, tavg), curses.A_BOLD)
1248 self.screen.refresh()
1249
1250 def _display_guest_dead(self):
1251 marker = ' Guest is DEAD '
1252 y = min(len(self._headline), 80 - len(marker))
1253 self.screen.addstr(0, y, marker, curses.A_BLINK | curses.A_STANDOUT)
1254
1255 def _show_msg(self, text):
1256 """Display message centered text and exit on key press"""
1257 hint = 'Press any key to continue'
1258 curses.cbreak()
1259 self.screen.erase()
1260 (x, term_width) = self.screen.getmaxyx()
1261 row = 2
1262 for line in text:
1263 start = (term_width - len(line)) // 2
1264 self.screen.addstr(row, start, line)
1265 row += 1
1266 self.screen.addstr(row + 1, (term_width - len(hint)) // 2, hint,
1267 curses.A_STANDOUT)
1268 self.screen.getkey()
1269
1270 def _show_help_interactive(self):
1271 """Display help with list of interactive commands"""
1272 msg = (' b toggle events by guests (debugfs only, honors'
1273 ' filters)',
1274 ' c clear filter',
1275 ' f filter by regular expression',
1276 ' g filter by guest name/PID',
1277 ' h display interactive commands reference',
1278 ' o toggle sorting order (Total vs CurAvg/s)',
1279 ' p filter by guest name/PID',
1280 ' q quit',
1281 ' r reset stats',
1282 ' s set update interval',
1283 ' x toggle reporting of stats for individual child trace'
1284 ' events',
1285 'Any other key refreshes statistics immediately')
1286 curses.cbreak()
1287 self.screen.erase()
1288 self.screen.addstr(0, 0, "Interactive commands reference",
1289 curses.A_BOLD)
1290 self.screen.addstr(2, 0, "Press any key to exit", curses.A_STANDOUT)
1291 row = 4
1292 for line in msg:
1293 self.screen.addstr(row, 0, line)
1294 row += 1
1295 self.screen.getkey()
1296 self._refresh_header()
1297
1298 def _show_filter_selection(self):
1299 """Draws filter selection mask.
1300
1301 Asks for a valid regex and sets the fields filter accordingly.
1302
1303 """
1304 msg = ''
1305 while True:
1306 self.screen.erase()
1307 self.screen.addstr(0, 0,
1308 "Show statistics for events matching a regex.",
1309 curses.A_BOLD)
1310 self.screen.addstr(2, 0,
1311 "Current regex: {0}"
1312 .format(self.stats.fields_filter))
1313 self.screen.addstr(5, 0, msg)
1314 self.screen.addstr(3, 0, "New regex: ")
1315 curses.echo()
1316 regex = self.screen.getstr().decode(ENCODING)
1317 curses.noecho()
1318 if len(regex) == 0:
1319 self.stats.fields_filter = ''
1320 self._refresh_header()
1321 return
1322 try:
1323 re.compile(regex)
1324 self.stats.fields_filter = regex
1325 self._refresh_header()
1326 return
1327 except re.error:
1328 msg = '"' + regex + '": Not a valid regular expression'
1329 continue
1330
1331 def _show_set_update_interval(self):
1332 """Draws update interval selection mask."""
1333 msg = ''
1334 while True:
1335 self.screen.erase()
1336 self.screen.addstr(0, 0, 'Set update interval (defaults to %.1fs).' %
1337 DELAY_DEFAULT, curses.A_BOLD)
1338 self.screen.addstr(4, 0, msg)
1339 self.screen.addstr(2, 0, 'Change delay from %.1fs to ' %
1340 self._delay_regular)
1341 curses.echo()
1342 val = self.screen.getstr().decode(ENCODING)
1343 curses.noecho()
1344
1345 try:
1346 if len(val) > 0:
1347 delay = float(val)
1348 if delay < 0.1:
1349 msg = '"' + str(val) + '": Value must be >=0.1'
1350 continue
1351 if delay > 25.5:
1352 msg = '"' + str(val) + '": Value must be <=25.5'
1353 continue
1354 else:
1355 delay = DELAY_DEFAULT
1356 self._delay_regular = delay
1357 break
1358
1359 except ValueError:
1360 msg = '"' + str(val) + '": Invalid value'
1361 self._refresh_header()
1362
1363 def _is_running_guest(self, pid):
1364 """Check if pid is still a running process."""
1365 if not pid:
1366 return True
1367 return os.path.isdir(os.path.join('/proc/', str(pid)))
1368
1369 def _show_vm_selection_by_guest(self):
1370 """Draws guest selection mask.
1371
1372 Asks for a guest name or pid until a valid guest name or '' is entered.
1373
1374 """
1375 msg = ''
1376 while True:
1377 self.screen.erase()
1378 self.screen.addstr(0, 0,
1379 'Show statistics for specific guest or pid.',
1380 curses.A_BOLD)
1381 self.screen.addstr(1, 0,
1382 'This might limit the shown data to the trace '
1383 'statistics.')
1384 self.screen.addstr(5, 0, msg)
1385 self._print_all_gnames(7)
1386 curses.echo()
1387 curses.curs_set(1)
1388 self.screen.addstr(3, 0, "Guest or pid [ENTER exits]: ")
1389 guest = self.screen.getstr().decode(ENCODING)
1390 curses.noecho()
1391
1392 pid = 0
1393 if not guest or guest == '0':
1394 break
1395 if guest.isdigit():
1396 if not self._is_running_guest(guest):
1397 msg = '"' + guest + '": Not a running process'
1398 continue
1399 pid = int(guest)
1400 break
1401 pids = []
1402 try:
1403 pids = self.get_pid_from_gname(guest)
1404 except:
1405 msg = '"' + guest + '": Internal error while searching, ' \
1406 'use pid filter instead'
1407 continue
1408 if len(pids) == 0:
1409 msg = '"' + guest + '": Not an active guest'
1410 continue
1411 if len(pids) > 1:
1412 msg = '"' + guest + '": Multiple matches found, use pid ' \
1413 'filter instead'
1414 continue
1415 pid = pids[0]
1416 break
1417 curses.curs_set(0)
1418 self._refresh_header(pid)
1419 self._update_pid(pid)
1420
1421 def show_stats(self):
1422 """Refreshes the screen and processes user input."""
1423 sleeptime = self._delay_initial
1424 self._refresh_header()
1425 start = 0.0 # result based on init value never appears on screen
1426 while True:
1427 self._refresh_body(time.time() - start)
1428 curses.halfdelay(int(sleeptime * 10))
1429 start = time.time()
1430 sleeptime = self._delay_regular
1431 try:
1432 char = self.screen.getkey()
1433 if char == 'b':
1434 self._display_guests = not self._display_guests
1435 if self.stats.toggle_display_guests(self._display_guests):
1436 self._show_msg(['Command not available with '
1437 'tracepoints enabled', 'Restart with '
1438 'debugfs only (see option \'-d\') and '
1439 'try again!'])
1440 self._display_guests = not self._display_guests
1441 self._refresh_header()
1442 if char == 'c':
1443 self.stats.fields_filter = ''
1444 self._refresh_header(0)
1445 self._update_pid(0)
1446 if char == 'f':
1447 curses.curs_set(1)
1448 self._show_filter_selection()
1449 curses.curs_set(0)
1450 sleeptime = self._delay_initial
1451 if char == 'g' or char == 'p':
1452 self._show_vm_selection_by_guest()
1453 sleeptime = self._delay_initial
1454 if char == 'h':
1455 self._show_help_interactive()
1456 if char == 'o':
1457 self._sorting = not self._sorting
1458 if char == 'q':
1459 break
1460 if char == 'r':
1461 self.stats.reset()
1462 if char == 's':
1463 curses.curs_set(1)
1464 self._show_set_update_interval()
1465 curses.curs_set(0)
1466 sleeptime = self._delay_initial
1467 if char == 'x':
1468 self.stats.child_events = not self.stats.child_events
1469 except KeyboardInterrupt:
1470 break
1471 except curses.error:
1472 continue
1473
1474
1475 def batch(stats):
1476 """Prints statistics in a key, value format."""
1477 try:
1478 s = stats.get()
1479 time.sleep(1)
1480 s = stats.get()
1481 for key, values in sorted(s.items()):
1482 print('%-42s%10d%10d' % (key.split(' ')[0], values.value,
1483 values.delta))
1484 except KeyboardInterrupt:
1485 pass
1486
1487
1488 def log(stats):
1489 """Prints statistics as reiterating key block, multiple value blocks."""
1490 keys = sorted(stats.get().keys())
1491
1492 def banner():
1493 for key in keys:
1494 print(key.split(' ')[0], end=' ')
1495 print()
1496
1497 def statline():
1498 s = stats.get()
1499 for key in keys:
1500 print(' %9d' % s[key].delta, end=' ')
1501 print()
1502 line = 0
1503 banner_repeat = 20
1504 while True:
1505 try:
1506 time.sleep(1)
1507 if line % banner_repeat == 0:
1508 banner()
1509 statline()
1510 line += 1
1511 except KeyboardInterrupt:
1512 break
1513
1514
1515 def get_options():
1516 """Returns processed program arguments."""
1517 description_text = """
1518 This script displays various statistics about VMs running under KVM.
1519 The statistics are gathered from the KVM debugfs entries and / or the
1520 currently available perf traces.
1521
1522 The monitoring takes additional cpu cycles and might affect the VM's
1523 performance.
1524
1525 Requirements:
1526 - Access to:
1527 %s
1528 %s/events/*
1529 /proc/pid/task
1530 - /proc/sys/kernel/perf_event_paranoid < 1 if user has no
1531 CAP_SYS_ADMIN and perf events are used.
1532 - CAP_SYS_RESOURCE if the hard limit is not high enough to allow
1533 the large number of files that are possibly opened.
1534
1535 Interactive Commands:
1536 b toggle events by guests (debugfs only, honors filters)
1537 c clear filter
1538 f filter by regular expression
1539 g filter by guest name
1540 h display interactive commands reference
1541 o toggle sorting order (Total vs CurAvg/s)
1542 p filter by PID
1543 q quit
1544 r reset stats
1545 s set update interval
1546 x toggle reporting of stats for individual child trace events
1547 Press any other key to refresh statistics immediately.
1548 """ % (PATH_DEBUGFS_KVM, PATH_DEBUGFS_TRACING)
1549
1550 class PlainHelpFormatter(optparse.IndentedHelpFormatter):
1551 def format_description(self, description):
1552 if description:
1553 return description + "\n"
1554 else:
1555 return ""
1556
1557 def cb_guest_to_pid(option, opt, val, parser):
1558 try:
1559 pids = Tui.get_pid_from_gname(val)
1560 except:
1561 sys.exit('Error while searching for guest "{}". Use "-p" to '
1562 'specify a pid instead?'.format(val))
1563 if len(pids) == 0:
1564 sys.exit('Error: No guest by the name "{}" found'.format(val))
1565 if len(pids) > 1:
1566 sys.exit('Error: Multiple processes found (pids: {}). Use "-p" '
1567 'to specify the desired pid'.format(" ".join(pids)))
1568 parser.values.pid = pids[0]
1569
1570 optparser = optparse.OptionParser(description=description_text,
1571 formatter=PlainHelpFormatter())
1572 optparser.add_option('-1', '--once', '--batch',
1573 action='store_true',
1574 default=False,
1575 dest='once',
1576 help='run in batch mode for one second',
1577 )
1578 optparser.add_option('-i', '--debugfs-include-past',
1579 action='store_true',
1580 default=False,
1581 dest='dbgfs_include_past',
1582 help='include all available data on past events for '
1583 'debugfs',
1584 )
1585 optparser.add_option('-l', '--log',
1586 action='store_true',
1587 default=False,
1588 dest='log',
1589 help='run in logging mode (like vmstat)',
1590 )
1591 optparser.add_option('-t', '--tracepoints',
1592 action='store_true',
1593 default=False,
1594 dest='tracepoints',
1595 help='retrieve statistics from tracepoints',
1596 )
1597 optparser.add_option('-d', '--debugfs',
1598 action='store_true',
1599 default=False,
1600 dest='debugfs',
1601 help='retrieve statistics from debugfs',
1602 )
1603 optparser.add_option('-f', '--fields',
1604 action='store',
1605 default='',
1606 dest='fields',
1607 help='''fields to display (regex)
1608 "-f help" for a list of available events''',
1609 )
1610 optparser.add_option('-p', '--pid',
1611 action='store',
1612 default=0,
1613 type='int',
1614 dest='pid',
1615 help='restrict statistics to pid',
1616 )
1617 optparser.add_option('-g', '--guest',
1618 action='callback',
1619 type='string',
1620 dest='pid',
1621 metavar='GUEST',
1622 help='restrict statistics to guest by name',
1623 callback=cb_guest_to_pid,
1624 )
1625 options, unkn = optparser.parse_args(sys.argv)
1626 if len(unkn) != 1:
1627 sys.exit('Error: Extra argument(s): ' + ' '.join(unkn[1:]))
1628 try:
1629 # verify that we were passed a valid regex up front
1630 re.compile(options.fields)
1631 except re.error:
1632 sys.exit('Error: "' + options.fields + '" is not a valid regular '
1633 'expression')
1634
1635 return options
1636
1637
1638 def check_access(options):
1639 """Exits if the current user can't access all needed directories."""
1640 if not os.path.exists(PATH_DEBUGFS_TRACING) and (options.tracepoints or
1641 not options.debugfs):
1642 sys.stderr.write("Please enable CONFIG_TRACING in your kernel "
1643 "when using the option -t (default).\n"
1644 "If it is enabled, make {0} readable by the "
1645 "current user.\n"
1646 .format(PATH_DEBUGFS_TRACING))
1647 if options.tracepoints:
1648 sys.exit(1)
1649
1650 sys.stderr.write("Falling back to debugfs statistics!\n")
1651 options.debugfs = True
1652 time.sleep(5)
1653
1654 return options
1655
1656
1657 def assign_globals():
1658 global PATH_DEBUGFS_KVM
1659 global PATH_DEBUGFS_TRACING
1660
1661 debugfs = ''
1662 for line in open('/proc/mounts'):
1663 if line.split(' ')[0] == 'debugfs':
1664 debugfs = line.split(' ')[1]
1665 break
1666 if debugfs == '':
1667 sys.stderr.write("Please make sure that CONFIG_DEBUG_FS is enabled in "
1668 "your kernel, mounted and\nreadable by the current "
1669 "user:\n"
1670 "('mount -t debugfs debugfs /sys/kernel/debug')\n")
1671 sys.exit(1)
1672
1673 PATH_DEBUGFS_KVM = os.path.join(debugfs, 'kvm')
1674 PATH_DEBUGFS_TRACING = os.path.join(debugfs, 'tracing')
1675
1676 if not os.path.exists(PATH_DEBUGFS_KVM):
1677 sys.stderr.write("Please make sure that CONFIG_KVM is enabled in "
1678 "your kernel and that the modules are loaded.\n")
1679 sys.exit(1)
1680
1681
1682 def main():
1683 assign_globals()
1684 options = get_options()
1685 options = check_access(options)
1686
1687 if (options.pid > 0 and
1688 not os.path.isdir(os.path.join('/proc/',
1689 str(options.pid)))):
1690 sys.stderr.write('Did you use a (unsupported) tid instead of a pid?\n')
1691 sys.exit('Specified pid does not exist.')
1692
1693 stats = Stats(options)
1694
1695 if options.fields == 'help':
1696 stats.fields_filter = None
1697 event_list = []
1698 for key in stats.get().keys():
1699 event_list.append(key.split('(', 1)[0])
1700 sys.stdout.write(' ' + '\n '.join(sorted(set(event_list))) + '\n')
1701 sys.exit(0)
1702
1703 if options.log:
1704 log(stats)
1705 elif not options.once:
1706 with Tui(stats) as tui:
1707 tui.show_stats()
1708 else:
1709 batch(stats)
1710
1711 if __name__ == "__main__":
1712 main()