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