]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blob - tools/kvm/kvm_stat/kvm_stat
f2a868b696a8cd9898c210db09337392083ff0c7
[mirror_ubuntu-bionic-kernel.git] / tools / kvm / kvm_stat / kvm_stat
1 #!/usr/bin/python
2 #
3 # top-like utility for displaying kvm statistics
4 #
5 # Copyright 2006-2008 Qumranet Technologies
6 # Copyright 2008-2011 Red Hat, Inc.
7 #
8 # Authors:
9 # Avi Kivity <avi@redhat.com>
10 #
11 # This work is licensed under the terms of the GNU GPL, version 2. See
12 # the COPYING file in the top-level directory.
13 """The kvm_stat module outputs statistics about running KVM VMs
14
15 Three different ways of output formatting are available:
16 - as a top-like text ui
17 - in a key -> value format
18 - in an all keys, all values format
19
20 The data is sampled from the KVM's debugfs entries and its perf events.
21 """
22
23 import curses
24 import sys
25 import os
26 import time
27 import optparse
28 import ctypes
29 import fcntl
30 import resource
31 import struct
32 import re
33 from collections import defaultdict
34
35 VMX_EXIT_REASONS = {
36 'EXCEPTION_NMI': 0,
37 'EXTERNAL_INTERRUPT': 1,
38 'TRIPLE_FAULT': 2,
39 'PENDING_INTERRUPT': 7,
40 'NMI_WINDOW': 8,
41 'TASK_SWITCH': 9,
42 'CPUID': 10,
43 'HLT': 12,
44 'INVLPG': 14,
45 'RDPMC': 15,
46 'RDTSC': 16,
47 'VMCALL': 18,
48 'VMCLEAR': 19,
49 'VMLAUNCH': 20,
50 'VMPTRLD': 21,
51 'VMPTRST': 22,
52 'VMREAD': 23,
53 'VMRESUME': 24,
54 'VMWRITE': 25,
55 'VMOFF': 26,
56 'VMON': 27,
57 'CR_ACCESS': 28,
58 'DR_ACCESS': 29,
59 'IO_INSTRUCTION': 30,
60 'MSR_READ': 31,
61 'MSR_WRITE': 32,
62 'INVALID_STATE': 33,
63 'MWAIT_INSTRUCTION': 36,
64 'MONITOR_INSTRUCTION': 39,
65 'PAUSE_INSTRUCTION': 40,
66 'MCE_DURING_VMENTRY': 41,
67 'TPR_BELOW_THRESHOLD': 43,
68 'APIC_ACCESS': 44,
69 'EPT_VIOLATION': 48,
70 'EPT_MISCONFIG': 49,
71 'WBINVD': 54,
72 'XSETBV': 55,
73 'APIC_WRITE': 56,
74 'INVPCID': 58,
75 }
76
77 SVM_EXIT_REASONS = {
78 'READ_CR0': 0x000,
79 'READ_CR3': 0x003,
80 'READ_CR4': 0x004,
81 'READ_CR8': 0x008,
82 'WRITE_CR0': 0x010,
83 'WRITE_CR3': 0x013,
84 'WRITE_CR4': 0x014,
85 'WRITE_CR8': 0x018,
86 'READ_DR0': 0x020,
87 'READ_DR1': 0x021,
88 'READ_DR2': 0x022,
89 'READ_DR3': 0x023,
90 'READ_DR4': 0x024,
91 'READ_DR5': 0x025,
92 'READ_DR6': 0x026,
93 'READ_DR7': 0x027,
94 'WRITE_DR0': 0x030,
95 'WRITE_DR1': 0x031,
96 'WRITE_DR2': 0x032,
97 'WRITE_DR3': 0x033,
98 'WRITE_DR4': 0x034,
99 'WRITE_DR5': 0x035,
100 'WRITE_DR6': 0x036,
101 'WRITE_DR7': 0x037,
102 'EXCP_BASE': 0x040,
103 'INTR': 0x060,
104 'NMI': 0x061,
105 'SMI': 0x062,
106 'INIT': 0x063,
107 'VINTR': 0x064,
108 'CR0_SEL_WRITE': 0x065,
109 'IDTR_READ': 0x066,
110 'GDTR_READ': 0x067,
111 'LDTR_READ': 0x068,
112 'TR_READ': 0x069,
113 'IDTR_WRITE': 0x06a,
114 'GDTR_WRITE': 0x06b,
115 'LDTR_WRITE': 0x06c,
116 'TR_WRITE': 0x06d,
117 'RDTSC': 0x06e,
118 'RDPMC': 0x06f,
119 'PUSHF': 0x070,
120 'POPF': 0x071,
121 'CPUID': 0x072,
122 'RSM': 0x073,
123 'IRET': 0x074,
124 'SWINT': 0x075,
125 'INVD': 0x076,
126 'PAUSE': 0x077,
127 'HLT': 0x078,
128 'INVLPG': 0x079,
129 'INVLPGA': 0x07a,
130 'IOIO': 0x07b,
131 'MSR': 0x07c,
132 'TASK_SWITCH': 0x07d,
133 'FERR_FREEZE': 0x07e,
134 'SHUTDOWN': 0x07f,
135 'VMRUN': 0x080,
136 'VMMCALL': 0x081,
137 'VMLOAD': 0x082,
138 'VMSAVE': 0x083,
139 'STGI': 0x084,
140 'CLGI': 0x085,
141 'SKINIT': 0x086,
142 'RDTSCP': 0x087,
143 'ICEBP': 0x088,
144 'WBINVD': 0x089,
145 'MONITOR': 0x08a,
146 'MWAIT': 0x08b,
147 'MWAIT_COND': 0x08c,
148 'XSETBV': 0x08d,
149 'NPF': 0x400,
150 }
151
152 # EC definition of HSR (from arch/arm64/include/asm/kvm_arm.h)
153 AARCH64_EXIT_REASONS = {
154 'UNKNOWN': 0x00,
155 'WFI': 0x01,
156 'CP15_32': 0x03,
157 'CP15_64': 0x04,
158 'CP14_MR': 0x05,
159 'CP14_LS': 0x06,
160 'FP_ASIMD': 0x07,
161 'CP10_ID': 0x08,
162 'CP14_64': 0x0C,
163 'ILL_ISS': 0x0E,
164 'SVC32': 0x11,
165 'HVC32': 0x12,
166 'SMC32': 0x13,
167 'SVC64': 0x15,
168 'HVC64': 0x16,
169 'SMC64': 0x17,
170 'SYS64': 0x18,
171 'IABT': 0x20,
172 'IABT_HYP': 0x21,
173 'PC_ALIGN': 0x22,
174 'DABT': 0x24,
175 'DABT_HYP': 0x25,
176 'SP_ALIGN': 0x26,
177 'FP_EXC32': 0x28,
178 'FP_EXC64': 0x2C,
179 'SERROR': 0x2F,
180 'BREAKPT': 0x30,
181 'BREAKPT_HYP': 0x31,
182 'SOFTSTP': 0x32,
183 'SOFTSTP_HYP': 0x33,
184 'WATCHPT': 0x34,
185 'WATCHPT_HYP': 0x35,
186 'BKPT32': 0x38,
187 'VECTOR32': 0x3A,
188 'BRK64': 0x3C,
189 }
190
191 # From include/uapi/linux/kvm.h, KVM_EXIT_xxx
192 USERSPACE_EXIT_REASONS = {
193 'UNKNOWN': 0,
194 'EXCEPTION': 1,
195 'IO': 2,
196 'HYPERCALL': 3,
197 'DEBUG': 4,
198 'HLT': 5,
199 'MMIO': 6,
200 'IRQ_WINDOW_OPEN': 7,
201 'SHUTDOWN': 8,
202 'FAIL_ENTRY': 9,
203 'INTR': 10,
204 'SET_TPR': 11,
205 'TPR_ACCESS': 12,
206 'S390_SIEIC': 13,
207 'S390_RESET': 14,
208 'DCR': 15,
209 'NMI': 16,
210 'INTERNAL_ERROR': 17,
211 'OSI': 18,
212 'PAPR_HCALL': 19,
213 'S390_UCONTROL': 20,
214 'WATCHDOG': 21,
215 'S390_TSCH': 22,
216 'EPR': 23,
217 'SYSTEM_EVENT': 24,
218 }
219
220 IOCTL_NUMBERS = {
221 'SET_FILTER': 0x40082406,
222 'ENABLE': 0x00002400,
223 'DISABLE': 0x00002401,
224 'RESET': 0x00002403,
225 }
226
227
228 class Arch(object):
229 """Encapsulates global architecture specific data.
230
231 Contains the performance event open syscall and ioctl numbers, as
232 well as the VM exit reasons for the architecture it runs on.
233
234 """
235 @staticmethod
236 def get_arch():
237 machine = os.uname()[4]
238
239 if machine.startswith('ppc'):
240 return ArchPPC()
241 elif machine.startswith('aarch64'):
242 return ArchA64()
243 elif machine.startswith('s390'):
244 return ArchS390()
245 else:
246 # X86_64
247 for line in open('/proc/cpuinfo'):
248 if not line.startswith('flags'):
249 continue
250
251 flags = line.split()
252 if 'vmx' in flags:
253 return ArchX86(VMX_EXIT_REASONS)
254 if 'svm' in flags:
255 return ArchX86(SVM_EXIT_REASONS)
256 return
257
258
259 class ArchX86(Arch):
260 def __init__(self, exit_reasons):
261 self.sc_perf_evt_open = 298
262 self.ioctl_numbers = IOCTL_NUMBERS
263 self.exit_reasons = exit_reasons
264
265
266 class ArchPPC(Arch):
267 def __init__(self):
268 self.sc_perf_evt_open = 319
269 self.ioctl_numbers = IOCTL_NUMBERS
270 self.ioctl_numbers['ENABLE'] = 0x20002400
271 self.ioctl_numbers['DISABLE'] = 0x20002401
272 self.ioctl_numbers['RESET'] = 0x20002403
273
274 # PPC comes in 32 and 64 bit and some generated ioctl
275 # numbers depend on the wordsize.
276 char_ptr_size = ctypes.sizeof(ctypes.c_char_p)
277 self.ioctl_numbers['SET_FILTER'] = 0x80002406 | char_ptr_size << 16
278 self.exit_reasons = {}
279
280
281 class ArchA64(Arch):
282 def __init__(self):
283 self.sc_perf_evt_open = 241
284 self.ioctl_numbers = IOCTL_NUMBERS
285 self.exit_reasons = AARCH64_EXIT_REASONS
286
287
288 class ArchS390(Arch):
289 def __init__(self):
290 self.sc_perf_evt_open = 331
291 self.ioctl_numbers = IOCTL_NUMBERS
292 self.exit_reasons = None
293
294 ARCH = Arch.get_arch()
295
296
297 def walkdir(path):
298 """Returns os.walk() data for specified directory.
299
300 As it is only a wrapper it returns the same 3-tuple of (dirpath,
301 dirnames, filenames).
302 """
303 return next(os.walk(path))
304
305
306 def parse_int_list(list_string):
307 """Returns an int list from a string of comma separated integers and
308 integer ranges."""
309 integers = []
310 members = list_string.split(',')
311
312 for member in members:
313 if '-' not in member:
314 integers.append(int(member))
315 else:
316 int_range = member.split('-')
317 integers.extend(range(int(int_range[0]),
318 int(int_range[1]) + 1))
319
320 return integers
321
322
323 def get_gname_from_pid(pid):
324 """Returns the guest name for a QEMU process pid.
325
326 Extracts the guest name from the QEMU comma line by processing the '-name'
327 option. Will also handle names specified out of sequence.
328
329 """
330 name = ''
331 try:
332 line = open('/proc/{}/cmdline'.format(pid), 'rb').read().split('\0')
333 parms = line[line.index('-name') + 1].split(',')
334 while '' in parms:
335 # commas are escaped (i.e. ',,'), hence e.g. 'foo,bar' results in
336 # ['foo', '', 'bar'], which we revert here
337 idx = parms.index('')
338 parms[idx - 1] += ',' + parms[idx + 1]
339 del parms[idx:idx+2]
340 # the '-name' switch allows for two ways to specify the guest name,
341 # where the plain name overrides the name specified via 'guest='
342 for arg in parms:
343 if '=' not in arg:
344 name = arg
345 break
346 if arg[:6] == 'guest=':
347 name = arg[6:]
348 except (ValueError, IOError, IndexError):
349 pass
350
351 return name
352
353
354 def get_online_cpus():
355 """Returns a list of cpu id integers."""
356 with open('/sys/devices/system/cpu/online') as cpu_list:
357 cpu_string = cpu_list.readline()
358 return parse_int_list(cpu_string)
359
360
361 def get_filters():
362 """Returns a dict of trace events, their filter ids and
363 the values that can be filtered.
364
365 Trace events can be filtered for special values by setting a
366 filter string via an ioctl. The string normally has the format
367 identifier==value. For each filter a new event will be created, to
368 be able to distinguish the events.
369
370 """
371 filters = {}
372 filters['kvm_userspace_exit'] = ('reason', USERSPACE_EXIT_REASONS)
373 if ARCH.exit_reasons:
374 filters['kvm_exit'] = ('exit_reason', ARCH.exit_reasons)
375 return filters
376
377 libc = ctypes.CDLL('libc.so.6', use_errno=True)
378 syscall = libc.syscall
379
380
381 class perf_event_attr(ctypes.Structure):
382 """Struct that holds the necessary data to set up a trace event.
383
384 For an extensive explanation see perf_event_open(2) and
385 include/uapi/linux/perf_event.h, struct perf_event_attr
386
387 All fields that are not initialized in the constructor are 0.
388
389 """
390 _fields_ = [('type', ctypes.c_uint32),
391 ('size', ctypes.c_uint32),
392 ('config', ctypes.c_uint64),
393 ('sample_freq', ctypes.c_uint64),
394 ('sample_type', ctypes.c_uint64),
395 ('read_format', ctypes.c_uint64),
396 ('flags', ctypes.c_uint64),
397 ('wakeup_events', ctypes.c_uint32),
398 ('bp_type', ctypes.c_uint32),
399 ('bp_addr', ctypes.c_uint64),
400 ('bp_len', ctypes.c_uint64),
401 ]
402
403 def __init__(self):
404 super(self.__class__, self).__init__()
405 self.type = PERF_TYPE_TRACEPOINT
406 self.size = ctypes.sizeof(self)
407 self.read_format = PERF_FORMAT_GROUP
408
409
410 def perf_event_open(attr, pid, cpu, group_fd, flags):
411 """Wrapper for the sys_perf_evt_open() syscall.
412
413 Used to set up performance events, returns a file descriptor or -1
414 on error.
415
416 Attributes are:
417 - syscall number
418 - struct perf_event_attr *
419 - pid or -1 to monitor all pids
420 - cpu number or -1 to monitor all cpus
421 - The file descriptor of the group leader or -1 to create a group.
422 - flags
423
424 """
425 return syscall(ARCH.sc_perf_evt_open, ctypes.pointer(attr),
426 ctypes.c_int(pid), ctypes.c_int(cpu),
427 ctypes.c_int(group_fd), ctypes.c_long(flags))
428
429 PERF_TYPE_TRACEPOINT = 2
430 PERF_FORMAT_GROUP = 1 << 3
431
432 PATH_DEBUGFS_TRACING = '/sys/kernel/debug/tracing'
433 PATH_DEBUGFS_KVM = '/sys/kernel/debug/kvm'
434
435
436 class Group(object):
437 """Represents a perf event group."""
438
439 def __init__(self):
440 self.events = []
441
442 def add_event(self, event):
443 self.events.append(event)
444
445 def read(self):
446 """Returns a dict with 'event name: value' for all events in the
447 group.
448
449 Values are read by reading from the file descriptor of the
450 event that is the group leader. See perf_event_open(2) for
451 details.
452
453 Read format for the used event configuration is:
454 struct read_format {
455 u64 nr; /* The number of events */
456 struct {
457 u64 value; /* The value of the event */
458 } values[nr];
459 };
460
461 """
462 length = 8 * (1 + len(self.events))
463 read_format = 'xxxxxxxx' + 'Q' * len(self.events)
464 return dict(zip([event.name for event in self.events],
465 struct.unpack(read_format,
466 os.read(self.events[0].fd, length))))
467
468
469 class Event(object):
470 """Represents a performance event and manages its life cycle."""
471 def __init__(self, name, group, trace_cpu, trace_pid, trace_point,
472 trace_filter, trace_set='kvm'):
473 self.name = name
474 self.fd = None
475 self.setup_event(group, trace_cpu, trace_pid, trace_point,
476 trace_filter, trace_set)
477
478 def __del__(self):
479 """Closes the event's file descriptor.
480
481 As no python file object was created for the file descriptor,
482 python will not reference count the descriptor and will not
483 close it itself automatically, so we do it.
484
485 """
486 if self.fd:
487 os.close(self.fd)
488
489 def setup_event_attribute(self, trace_set, trace_point):
490 """Returns an initialized ctype perf_event_attr struct."""
491
492 id_path = os.path.join(PATH_DEBUGFS_TRACING, 'events', trace_set,
493 trace_point, 'id')
494
495 event_attr = perf_event_attr()
496 event_attr.config = int(open(id_path).read())
497 return event_attr
498
499 def setup_event(self, group, trace_cpu, trace_pid, trace_point,
500 trace_filter, trace_set):
501 """Sets up the perf event in Linux.
502
503 Issues the syscall to register the event in the kernel and
504 then sets the optional filter.
505
506 """
507
508 event_attr = self.setup_event_attribute(trace_set, trace_point)
509
510 # First event will be group leader.
511 group_leader = -1
512
513 # All others have to pass the leader's descriptor instead.
514 if group.events:
515 group_leader = group.events[0].fd
516
517 fd = perf_event_open(event_attr, trace_pid,
518 trace_cpu, group_leader, 0)
519 if fd == -1:
520 err = ctypes.get_errno()
521 raise OSError(err, os.strerror(err),
522 'while calling sys_perf_event_open().')
523
524 if trace_filter:
525 fcntl.ioctl(fd, ARCH.ioctl_numbers['SET_FILTER'],
526 trace_filter)
527
528 self.fd = fd
529
530 def enable(self):
531 """Enables the trace event in the kernel.
532
533 Enabling the group leader makes reading counters from it and the
534 events under it possible.
535
536 """
537 fcntl.ioctl(self.fd, ARCH.ioctl_numbers['ENABLE'], 0)
538
539 def disable(self):
540 """Disables the trace event in the kernel.
541
542 Disabling the group leader makes reading all counters under it
543 impossible.
544
545 """
546 fcntl.ioctl(self.fd, ARCH.ioctl_numbers['DISABLE'], 0)
547
548 def reset(self):
549 """Resets the count of the trace event in the kernel."""
550 fcntl.ioctl(self.fd, ARCH.ioctl_numbers['RESET'], 0)
551
552
553 class TracepointProvider(object):
554 """Data provider for the stats class.
555
556 Manages the events/groups from which it acquires its data.
557
558 """
559 def __init__(self):
560 self.group_leaders = []
561 self.filters = get_filters()
562 self._fields = self.get_available_fields()
563 self._pid = 0
564
565 def get_available_fields(self):
566 """Returns a list of available event's of format 'event name(filter
567 name)'.
568
569 All available events have directories under
570 /sys/kernel/debug/tracing/events/ which export information
571 about the specific event. Therefore, listing the dirs gives us
572 a list of all available events.
573
574 Some events like the vm exit reasons can be filtered for
575 specific values. To take account for that, the routine below
576 creates special fields with the following format:
577 event name(filter name)
578
579 """
580 path = os.path.join(PATH_DEBUGFS_TRACING, 'events', 'kvm')
581 fields = walkdir(path)[1]
582 extra = []
583 for field in fields:
584 if field in self.filters:
585 filter_name_, filter_dicts = self.filters[field]
586 for name in filter_dicts:
587 extra.append(field + '(' + name + ')')
588 fields += extra
589 return fields
590
591 def setup_traces(self):
592 """Creates all event and group objects needed to be able to retrieve
593 data."""
594 fields = self.get_available_fields()
595 if self._pid > 0:
596 # Fetch list of all threads of the monitored pid, as qemu
597 # starts a thread for each vcpu.
598 path = os.path.join('/proc', str(self._pid), 'task')
599 groupids = walkdir(path)[1]
600 else:
601 groupids = get_online_cpus()
602
603 # The constant is needed as a buffer for python libs, std
604 # streams and other files that the script opens.
605 newlim = len(groupids) * len(fields) + 50
606 try:
607 softlim_, hardlim = resource.getrlimit(resource.RLIMIT_NOFILE)
608
609 if hardlim < newlim:
610 # Now we need CAP_SYS_RESOURCE, to increase the hard limit.
611 resource.setrlimit(resource.RLIMIT_NOFILE, (newlim, newlim))
612 else:
613 # Raising the soft limit is sufficient.
614 resource.setrlimit(resource.RLIMIT_NOFILE, (newlim, hardlim))
615
616 except ValueError:
617 sys.exit("NOFILE rlimit could not be raised to {0}".format(newlim))
618
619 for groupid in groupids:
620 group = Group()
621 for name in fields:
622 tracepoint = name
623 tracefilter = None
624 match = re.match(r'(.*)\((.*)\)', name)
625 if match:
626 tracepoint, sub = match.groups()
627 tracefilter = ('%s==%d\0' %
628 (self.filters[tracepoint][0],
629 self.filters[tracepoint][1][sub]))
630
631 # From perf_event_open(2):
632 # pid > 0 and cpu == -1
633 # This measures the specified process/thread on any CPU.
634 #
635 # pid == -1 and cpu >= 0
636 # This measures all processes/threads on the specified CPU.
637 trace_cpu = groupid if self._pid == 0 else -1
638 trace_pid = int(groupid) if self._pid != 0 else -1
639
640 group.add_event(Event(name=name,
641 group=group,
642 trace_cpu=trace_cpu,
643 trace_pid=trace_pid,
644 trace_point=tracepoint,
645 trace_filter=tracefilter))
646
647 self.group_leaders.append(group)
648
649 def available_fields(self):
650 return self.get_available_fields()
651
652 @property
653 def fields(self):
654 return self._fields
655
656 @fields.setter
657 def fields(self, fields):
658 """Enables/disables the (un)wanted events"""
659 self._fields = fields
660 for group in self.group_leaders:
661 for index, event in enumerate(group.events):
662 if event.name in fields:
663 event.reset()
664 event.enable()
665 else:
666 # Do not disable the group leader.
667 # It would disable all of its events.
668 if index != 0:
669 event.disable()
670
671 @property
672 def pid(self):
673 return self._pid
674
675 @pid.setter
676 def pid(self, pid):
677 """Changes the monitored pid by setting new traces."""
678 self._pid = pid
679 # The garbage collector will get rid of all Event/Group
680 # objects and open files after removing the references.
681 self.group_leaders = []
682 self.setup_traces()
683 self.fields = self._fields
684
685 def read(self):
686 """Returns 'event name: current value' for all enabled events."""
687 ret = defaultdict(int)
688 for group in self.group_leaders:
689 for name, val in group.read().iteritems():
690 if name in self._fields:
691 ret[name] += val
692 return ret
693
694
695 class DebugfsProvider(object):
696 """Provides data from the files that KVM creates in the kvm debugfs
697 folder."""
698 def __init__(self):
699 self._fields = self.get_available_fields()
700 self._pid = 0
701 self.do_read = True
702 self.paths = []
703
704 def get_available_fields(self):
705 """"Returns a list of available fields.
706
707 The fields are all available KVM debugfs files
708
709 """
710 return walkdir(PATH_DEBUGFS_KVM)[2]
711
712 @property
713 def fields(self):
714 return self._fields
715
716 @fields.setter
717 def fields(self, fields):
718 self._fields = fields
719
720 @property
721 def pid(self):
722 return self._pid
723
724 @pid.setter
725 def pid(self, pid):
726 if pid != 0:
727 self._pid = pid
728
729 vms = walkdir(PATH_DEBUGFS_KVM)[1]
730 if len(vms) == 0:
731 self.do_read = False
732
733 self.paths = filter(lambda x: "{}-".format(pid) in x, vms)
734
735 else:
736 self.paths = ['']
737 self.do_read = True
738
739 def read(self):
740 """Returns a dict with format:'file name / field -> current value'."""
741 results = {}
742
743 # If no debugfs filtering support is available, then don't read.
744 if not self.do_read:
745 return results
746
747 for path in self.paths:
748 for field in self._fields:
749 results[field] = results.get(field, 0) \
750 + self.read_field(field, path)
751
752 return results
753
754 def read_field(self, field, path):
755 """Returns the value of a single field from a specific VM."""
756 try:
757 return int(open(os.path.join(PATH_DEBUGFS_KVM,
758 path,
759 field))
760 .read())
761 except IOError:
762 return 0
763
764
765 class Stats(object):
766 """Manages the data providers and the data they provide.
767
768 It is used to set filters on the provider's data and collect all
769 provider data.
770
771 """
772 def __init__(self, providers, pid, fields=None):
773 self.providers = providers
774 self._pid_filter = pid
775 self._fields_filter = fields
776 self.values = {}
777 self.update_provider_pid()
778 self.update_provider_filters()
779
780 def update_provider_filters(self):
781 """Propagates fields filters to providers."""
782 def wanted(key):
783 if not self._fields_filter:
784 return True
785 return re.match(self._fields_filter, key) is not None
786
787 # As we reset the counters when updating the fields we can
788 # also clear the cache of old values.
789 self.values = {}
790 for provider in self.providers:
791 provider_fields = [key for key in provider.get_available_fields()
792 if wanted(key)]
793 provider.fields = provider_fields
794
795 def update_provider_pid(self):
796 """Propagates pid filters to providers."""
797 for provider in self.providers:
798 provider.pid = self._pid_filter
799
800 @property
801 def fields_filter(self):
802 return self._fields_filter
803
804 @fields_filter.setter
805 def fields_filter(self, fields_filter):
806 self._fields_filter = fields_filter
807 self.update_provider_filters()
808
809 @property
810 def pid_filter(self):
811 return self._pid_filter
812
813 @pid_filter.setter
814 def pid_filter(self, pid):
815 self._pid_filter = pid
816 self.values = {}
817 self.update_provider_pid()
818
819 def get(self):
820 """Returns a dict with field -> (value, delta to last value) of all
821 provider data."""
822 for provider in self.providers:
823 new = provider.read()
824 for key in provider.fields:
825 oldval = self.values.get(key, (0, 0))
826 newval = new.get(key, 0)
827 newdelta = None
828 if oldval is not None:
829 newdelta = newval - oldval[0]
830 self.values[key] = (newval, newdelta)
831 return self.values
832
833 LABEL_WIDTH = 40
834 NUMBER_WIDTH = 10
835 DELAY_INITIAL = 0.25
836 DELAY_REGULAR = 3.0
837 MAX_GUEST_NAME_LEN = 48
838 MAX_REGEX_LEN = 44
839
840
841 class Tui(object):
842 """Instruments curses to draw a nice text ui."""
843 def __init__(self, stats):
844 self.stats = stats
845 self.screen = None
846 self.update_drilldown()
847
848 def __enter__(self):
849 """Initialises curses for later use. Based on curses.wrapper
850 implementation from the Python standard library."""
851 self.screen = curses.initscr()
852 curses.noecho()
853 curses.cbreak()
854
855 # The try/catch works around a minor bit of
856 # over-conscientiousness in the curses module, the error
857 # return from C start_color() is ignorable.
858 try:
859 curses.start_color()
860 except curses.error:
861 pass
862
863 # Hide cursor in extra statement as some monochrome terminals
864 # might support hiding but not colors.
865 try:
866 curses.curs_set(0)
867 except curses.error:
868 pass
869
870 curses.use_default_colors()
871 return self
872
873 def __exit__(self, *exception):
874 """Resets the terminal to its normal state. Based on curses.wrappre
875 implementation from the Python standard library."""
876 if self.screen:
877 self.screen.keypad(0)
878 curses.echo()
879 curses.nocbreak()
880 curses.endwin()
881
882 def update_drilldown(self):
883 """Sets or removes a filter that only allows fields without braces."""
884 if not self.stats.fields_filter:
885 self.stats.fields_filter = r'^[^\(]*$'
886
887 elif self.stats.fields_filter == r'^[^\(]*$':
888 self.stats.fields_filter = None
889
890 def update_pid(self, pid):
891 """Propagates pid selection to stats object."""
892 self.stats.pid_filter = pid
893
894 def refresh_header(self, pid=None):
895 """Refreshes the header."""
896 if pid is None:
897 pid = self.stats.pid_filter
898 self.screen.erase()
899 gname = get_gname_from_pid(pid)
900 if gname:
901 gname = ('({})'.format(gname[:MAX_GUEST_NAME_LEN] + '...'
902 if len(gname) > MAX_GUEST_NAME_LEN
903 else gname))
904 if pid > 0:
905 self.screen.addstr(0, 0, 'kvm statistics - pid {0} {1}'
906 .format(pid, gname), curses.A_BOLD)
907 else:
908 self.screen.addstr(0, 0, 'kvm statistics - summary', curses.A_BOLD)
909 if self.stats.fields_filter and self.stats.fields_filter != '^[^\(]*$':
910 regex = self.stats.fields_filter
911 if len(regex) > MAX_REGEX_LEN:
912 regex = regex[:MAX_REGEX_LEN] + '...'
913 self.screen.addstr(1, 17, 'regex filter: {0}'.format(regex))
914 self.screen.addstr(2, 1, 'Event')
915 self.screen.addstr(2, 1 + LABEL_WIDTH + NUMBER_WIDTH -
916 len('Total'), 'Total')
917 self.screen.addstr(2, 1 + LABEL_WIDTH + NUMBER_WIDTH + 8 -
918 len('Current'), 'Current')
919 self.screen.addstr(4, 1, 'Collecting data...')
920 self.screen.refresh()
921
922 def refresh_body(self, sleeptime):
923 row = 3
924 self.screen.move(row, 0)
925 self.screen.clrtobot()
926 stats = self.stats.get()
927
928 def sortkey(x):
929 if stats[x][1]:
930 return (-stats[x][1], -stats[x][0])
931 else:
932 return (0, -stats[x][0])
933 for key in sorted(stats.keys(), key=sortkey):
934
935 if row >= self.screen.getmaxyx()[0]:
936 break
937 values = stats[key]
938 if not values[0] and not values[1]:
939 break
940 col = 1
941 self.screen.addstr(row, col, key)
942 col += LABEL_WIDTH
943 self.screen.addstr(row, col, '%10d' % (values[0],))
944 col += NUMBER_WIDTH
945 if values[1] is not None:
946 self.screen.addstr(row, col, '%8d' % (values[1] / sleeptime,))
947 row += 1
948 self.screen.refresh()
949
950 def show_filter_selection(self):
951 """Draws filter selection mask.
952
953 Asks for a valid regex and sets the fields filter accordingly.
954
955 """
956 while True:
957 self.screen.erase()
958 self.screen.addstr(0, 0,
959 "Show statistics for events matching a regex.",
960 curses.A_BOLD)
961 self.screen.addstr(2, 0,
962 "Current regex: {0}"
963 .format(self.stats.fields_filter))
964 self.screen.addstr(3, 0, "New regex: ")
965 curses.echo()
966 regex = self.screen.getstr()
967 curses.noecho()
968 if len(regex) == 0:
969 self.stats.fields_filter = r'^[^\(]*$'
970 self.refresh_header()
971 return
972 try:
973 re.compile(regex)
974 self.stats.fields_filter = regex
975 self.refresh_header()
976 return
977 except re.error:
978 continue
979
980 def show_vm_selection(self):
981 """Draws PID selection mask.
982
983 Asks for a pid until a valid pid or 0 has been entered.
984
985 """
986 msg = ''
987 while True:
988 self.screen.erase()
989 self.screen.addstr(0, 0,
990 'Show statistics for specific pid.',
991 curses.A_BOLD)
992 self.screen.addstr(1, 0,
993 'This might limit the shown data to the trace '
994 'statistics.')
995 self.screen.addstr(5, 0, msg)
996
997 curses.echo()
998 self.screen.addstr(3, 0, "Pid [0 or pid]: ")
999 pid = self.screen.getstr()
1000 curses.noecho()
1001
1002 try:
1003 if len(pid) > 0:
1004 pid = int(pid)
1005 if pid != 0 and not os.path.isdir(os.path.join('/proc/',
1006 str(pid))):
1007 msg = '"' + str(pid) + '": Not a running process'
1008 continue
1009 else:
1010 pid = 0
1011 self.refresh_header(pid)
1012 self.update_pid(pid)
1013 break
1014
1015 except ValueError:
1016 msg = '"' + str(pid) + '": Not a valid pid'
1017 continue
1018
1019 def show_stats(self):
1020 """Refreshes the screen and processes user input."""
1021 sleeptime = DELAY_INITIAL
1022 self.refresh_header()
1023 while True:
1024 self.refresh_body(sleeptime)
1025 curses.halfdelay(int(sleeptime * 10))
1026 sleeptime = DELAY_REGULAR
1027 try:
1028 char = self.screen.getkey()
1029 if char == 'x':
1030 self.refresh_header()
1031 self.update_drilldown()
1032 sleeptime = DELAY_INITIAL
1033 if char == 'q':
1034 break
1035 if char == 'f':
1036 self.show_filter_selection()
1037 sleeptime = DELAY_INITIAL
1038 if char == 'p':
1039 self.show_vm_selection()
1040 sleeptime = DELAY_INITIAL
1041 except KeyboardInterrupt:
1042 break
1043 except curses.error:
1044 continue
1045
1046
1047 def batch(stats):
1048 """Prints statistics in a key, value format."""
1049 try:
1050 s = stats.get()
1051 time.sleep(1)
1052 s = stats.get()
1053 for key in sorted(s.keys()):
1054 values = s[key]
1055 print '%-42s%10d%10d' % (key, values[0], values[1])
1056 except KeyboardInterrupt:
1057 pass
1058
1059
1060 def log(stats):
1061 """Prints statistics as reiterating key block, multiple value blocks."""
1062 keys = sorted(stats.get().iterkeys())
1063
1064 def banner():
1065 for k in keys:
1066 print '%s' % k,
1067 print
1068
1069 def statline():
1070 s = stats.get()
1071 for k in keys:
1072 print ' %9d' % s[k][1],
1073 print
1074 line = 0
1075 banner_repeat = 20
1076 while True:
1077 try:
1078 time.sleep(1)
1079 if line % banner_repeat == 0:
1080 banner()
1081 statline()
1082 line += 1
1083 except KeyboardInterrupt:
1084 break
1085
1086
1087 def get_options():
1088 """Returns processed program arguments."""
1089 description_text = """
1090 This script displays various statistics about VMs running under KVM.
1091 The statistics are gathered from the KVM debugfs entries and / or the
1092 currently available perf traces.
1093
1094 The monitoring takes additional cpu cycles and might affect the VM's
1095 performance.
1096
1097 Requirements:
1098 - Access to:
1099 /sys/kernel/debug/kvm
1100 /sys/kernel/debug/trace/events/*
1101 /proc/pid/task
1102 - /proc/sys/kernel/perf_event_paranoid < 1 if user has no
1103 CAP_SYS_ADMIN and perf events are used.
1104 - CAP_SYS_RESOURCE if the hard limit is not high enough to allow
1105 the large number of files that are possibly opened.
1106
1107 Interactive Commands:
1108 f filter by regular expression
1109 p filter by PID
1110 q quit
1111 x toggle reporting of stats for individual child trace events
1112 Press any other key to refresh statistics immediately.
1113 """
1114
1115 class PlainHelpFormatter(optparse.IndentedHelpFormatter):
1116 def format_description(self, description):
1117 if description:
1118 return description + "\n"
1119 else:
1120 return ""
1121
1122 optparser = optparse.OptionParser(description=description_text,
1123 formatter=PlainHelpFormatter())
1124 optparser.add_option('-1', '--once', '--batch',
1125 action='store_true',
1126 default=False,
1127 dest='once',
1128 help='run in batch mode for one second',
1129 )
1130 optparser.add_option('-l', '--log',
1131 action='store_true',
1132 default=False,
1133 dest='log',
1134 help='run in logging mode (like vmstat)',
1135 )
1136 optparser.add_option('-t', '--tracepoints',
1137 action='store_true',
1138 default=False,
1139 dest='tracepoints',
1140 help='retrieve statistics from tracepoints',
1141 )
1142 optparser.add_option('-d', '--debugfs',
1143 action='store_true',
1144 default=False,
1145 dest='debugfs',
1146 help='retrieve statistics from debugfs',
1147 )
1148 optparser.add_option('-f', '--fields',
1149 action='store',
1150 default=None,
1151 dest='fields',
1152 help='fields to display (regex)',
1153 )
1154 optparser.add_option('-p', '--pid',
1155 action='store',
1156 default=0,
1157 type='int',
1158 dest='pid',
1159 help='restrict statistics to pid',
1160 )
1161 (options, _) = optparser.parse_args(sys.argv)
1162 return options
1163
1164
1165 def get_providers(options):
1166 """Returns a list of data providers depending on the passed options."""
1167 providers = []
1168
1169 if options.tracepoints:
1170 providers.append(TracepointProvider())
1171 if options.debugfs:
1172 providers.append(DebugfsProvider())
1173 if len(providers) == 0:
1174 providers.append(TracepointProvider())
1175
1176 return providers
1177
1178
1179 def check_access(options):
1180 """Exits if the current user can't access all needed directories."""
1181 if not os.path.exists('/sys/kernel/debug'):
1182 sys.stderr.write('Please enable CONFIG_DEBUG_FS in your kernel.')
1183 sys.exit(1)
1184
1185 if not os.path.exists(PATH_DEBUGFS_KVM):
1186 sys.stderr.write("Please make sure, that debugfs is mounted and "
1187 "readable by the current user:\n"
1188 "('mount -t debugfs debugfs /sys/kernel/debug')\n"
1189 "Also ensure, that the kvm modules are loaded.\n")
1190 sys.exit(1)
1191
1192 if not os.path.exists(PATH_DEBUGFS_TRACING) and (options.tracepoints or
1193 not options.debugfs):
1194 sys.stderr.write("Please enable CONFIG_TRACING in your kernel "
1195 "when using the option -t (default).\n"
1196 "If it is enabled, make {0} readable by the "
1197 "current user.\n"
1198 .format(PATH_DEBUGFS_TRACING))
1199 if options.tracepoints:
1200 sys.exit(1)
1201
1202 sys.stderr.write("Falling back to debugfs statistics!\n")
1203 options.debugfs = True
1204 time.sleep(5)
1205
1206 return options
1207
1208
1209 def main():
1210 options = get_options()
1211 options = check_access(options)
1212
1213 if (options.pid > 0 and
1214 not os.path.isdir(os.path.join('/proc/',
1215 str(options.pid)))):
1216 sys.stderr.write('Did you use a (unsupported) tid instead of a pid?\n')
1217 sys.exit('Specified pid does not exist.')
1218
1219 providers = get_providers(options)
1220 stats = Stats(providers, options.pid, fields=options.fields)
1221
1222 if options.log:
1223 log(stats)
1224 elif not options.once:
1225 with Tui(stats) as tui:
1226 tui.show_stats()
1227 else:
1228 batch(stats)
1229
1230 if __name__ == "__main__":
1231 main()