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