]>
Commit | Line | Data |
---|---|---|
f9bc9e65 JF |
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 | ||
14 | import curses | |
15 | import sys | |
16 | import os | |
17 | import time | |
18 | import optparse | |
19 | import ctypes | |
20 | import fcntl | |
21 | import resource | |
22 | import struct | |
23 | import re | |
24 | from collections import defaultdict | |
25 | from time import sleep | |
26 | ||
27 | VMX_EXIT_REASONS = { | |
28 | 'EXCEPTION_NMI': 0, | |
29 | 'EXTERNAL_INTERRUPT': 1, | |
30 | 'TRIPLE_FAULT': 2, | |
31 | 'PENDING_INTERRUPT': 7, | |
32 | 'NMI_WINDOW': 8, | |
33 | 'TASK_SWITCH': 9, | |
34 | 'CPUID': 10, | |
35 | 'HLT': 12, | |
36 | 'INVLPG': 14, | |
37 | 'RDPMC': 15, | |
38 | 'RDTSC': 16, | |
39 | 'VMCALL': 18, | |
40 | 'VMCLEAR': 19, | |
41 | 'VMLAUNCH': 20, | |
42 | 'VMPTRLD': 21, | |
43 | 'VMPTRST': 22, | |
44 | 'VMREAD': 23, | |
45 | 'VMRESUME': 24, | |
46 | 'VMWRITE': 25, | |
47 | 'VMOFF': 26, | |
48 | 'VMON': 27, | |
49 | 'CR_ACCESS': 28, | |
50 | 'DR_ACCESS': 29, | |
51 | 'IO_INSTRUCTION': 30, | |
52 | 'MSR_READ': 31, | |
53 | 'MSR_WRITE': 32, | |
54 | 'INVALID_STATE': 33, | |
55 | 'MWAIT_INSTRUCTION': 36, | |
56 | 'MONITOR_INSTRUCTION': 39, | |
57 | 'PAUSE_INSTRUCTION': 40, | |
58 | 'MCE_DURING_VMENTRY': 41, | |
59 | 'TPR_BELOW_THRESHOLD': 43, | |
60 | 'APIC_ACCESS': 44, | |
61 | 'EPT_VIOLATION': 48, | |
62 | 'EPT_MISCONFIG': 49, | |
63 | 'WBINVD': 54, | |
64 | 'XSETBV': 55, | |
65 | 'APIC_WRITE': 56, | |
66 | 'INVPCID': 58, | |
67 | } | |
68 | ||
69 | SVM_EXIT_REASONS = { | |
70 | 'READ_CR0': 0x000, | |
71 | 'READ_CR3': 0x003, | |
72 | 'READ_CR4': 0x004, | |
73 | 'READ_CR8': 0x008, | |
74 | 'WRITE_CR0': 0x010, | |
75 | 'WRITE_CR3': 0x013, | |
76 | 'WRITE_CR4': 0x014, | |
77 | 'WRITE_CR8': 0x018, | |
78 | 'READ_DR0': 0x020, | |
79 | 'READ_DR1': 0x021, | |
80 | 'READ_DR2': 0x022, | |
81 | 'READ_DR3': 0x023, | |
82 | 'READ_DR4': 0x024, | |
83 | 'READ_DR5': 0x025, | |
84 | 'READ_DR6': 0x026, | |
85 | 'READ_DR7': 0x027, | |
86 | 'WRITE_DR0': 0x030, | |
87 | 'WRITE_DR1': 0x031, | |
88 | 'WRITE_DR2': 0x032, | |
89 | 'WRITE_DR3': 0x033, | |
90 | 'WRITE_DR4': 0x034, | |
91 | 'WRITE_DR5': 0x035, | |
92 | 'WRITE_DR6': 0x036, | |
93 | 'WRITE_DR7': 0x037, | |
94 | 'EXCP_BASE': 0x040, | |
95 | 'INTR': 0x060, | |
96 | 'NMI': 0x061, | |
97 | 'SMI': 0x062, | |
98 | 'INIT': 0x063, | |
99 | 'VINTR': 0x064, | |
100 | 'CR0_SEL_WRITE': 0x065, | |
101 | 'IDTR_READ': 0x066, | |
102 | 'GDTR_READ': 0x067, | |
103 | 'LDTR_READ': 0x068, | |
104 | 'TR_READ': 0x069, | |
105 | 'IDTR_WRITE': 0x06a, | |
106 | 'GDTR_WRITE': 0x06b, | |
107 | 'LDTR_WRITE': 0x06c, | |
108 | 'TR_WRITE': 0x06d, | |
109 | 'RDTSC': 0x06e, | |
110 | 'RDPMC': 0x06f, | |
111 | 'PUSHF': 0x070, | |
112 | 'POPF': 0x071, | |
113 | 'CPUID': 0x072, | |
114 | 'RSM': 0x073, | |
115 | 'IRET': 0x074, | |
116 | 'SWINT': 0x075, | |
117 | 'INVD': 0x076, | |
118 | 'PAUSE': 0x077, | |
119 | 'HLT': 0x078, | |
120 | 'INVLPG': 0x079, | |
121 | 'INVLPGA': 0x07a, | |
122 | 'IOIO': 0x07b, | |
123 | 'MSR': 0x07c, | |
124 | 'TASK_SWITCH': 0x07d, | |
125 | 'FERR_FREEZE': 0x07e, | |
126 | 'SHUTDOWN': 0x07f, | |
127 | 'VMRUN': 0x080, | |
128 | 'VMMCALL': 0x081, | |
129 | 'VMLOAD': 0x082, | |
130 | 'VMSAVE': 0x083, | |
131 | 'STGI': 0x084, | |
132 | 'CLGI': 0x085, | |
133 | 'SKINIT': 0x086, | |
134 | 'RDTSCP': 0x087, | |
135 | 'ICEBP': 0x088, | |
136 | 'WBINVD': 0x089, | |
137 | 'MONITOR': 0x08a, | |
138 | 'MWAIT': 0x08b, | |
139 | 'MWAIT_COND': 0x08c, | |
140 | 'XSETBV': 0x08d, | |
141 | 'NPF': 0x400, | |
142 | } | |
143 | ||
144 | # EC definition of HSR (from arch/arm64/include/asm/kvm_arm.h) | |
145 | AARCH64_EXIT_REASONS = { | |
146 | 'UNKNOWN': 0x00, | |
147 | 'WFI': 0x01, | |
148 | 'CP15_32': 0x03, | |
149 | 'CP15_64': 0x04, | |
150 | 'CP14_MR': 0x05, | |
151 | 'CP14_LS': 0x06, | |
152 | 'FP_ASIMD': 0x07, | |
153 | 'CP10_ID': 0x08, | |
154 | 'CP14_64': 0x0C, | |
155 | 'ILL_ISS': 0x0E, | |
156 | 'SVC32': 0x11, | |
157 | 'HVC32': 0x12, | |
158 | 'SMC32': 0x13, | |
159 | 'SVC64': 0x15, | |
160 | 'HVC64': 0x16, | |
161 | 'SMC64': 0x17, | |
162 | 'SYS64': 0x18, | |
163 | 'IABT': 0x20, | |
164 | 'IABT_HYP': 0x21, | |
165 | 'PC_ALIGN': 0x22, | |
166 | 'DABT': 0x24, | |
167 | 'DABT_HYP': 0x25, | |
168 | 'SP_ALIGN': 0x26, | |
169 | 'FP_EXC32': 0x28, | |
170 | 'FP_EXC64': 0x2C, | |
171 | 'SERROR': 0x2F, | |
172 | 'BREAKPT': 0x30, | |
173 | 'BREAKPT_HYP': 0x31, | |
174 | 'SOFTSTP': 0x32, | |
175 | 'SOFTSTP_HYP': 0x33, | |
176 | 'WATCHPT': 0x34, | |
177 | 'WATCHPT_HYP': 0x35, | |
178 | 'BKPT32': 0x38, | |
179 | 'VECTOR32': 0x3A, | |
180 | 'BRK64': 0x3C, | |
181 | } | |
182 | ||
183 | # From include/uapi/linux/kvm.h, KVM_EXIT_xxx | |
184 | USERSPACE_EXIT_REASONS = { | |
185 | 'UNKNOWN': 0, | |
186 | 'EXCEPTION': 1, | |
187 | 'IO': 2, | |
188 | 'HYPERCALL': 3, | |
189 | 'DEBUG': 4, | |
190 | 'HLT': 5, | |
191 | 'MMIO': 6, | |
192 | 'IRQ_WINDOW_OPEN': 7, | |
193 | 'SHUTDOWN': 8, | |
194 | 'FAIL_ENTRY': 9, | |
195 | 'INTR': 10, | |
196 | 'SET_TPR': 11, | |
197 | 'TPR_ACCESS': 12, | |
198 | 'S390_SIEIC': 13, | |
199 | 'S390_RESET': 14, | |
200 | 'DCR': 15, | |
201 | 'NMI': 16, | |
202 | 'INTERNAL_ERROR': 17, | |
203 | 'OSI': 18, | |
204 | 'PAPR_HCALL': 19, | |
205 | 'S390_UCONTROL': 20, | |
206 | 'WATCHDOG': 21, | |
207 | 'S390_TSCH': 22, | |
208 | 'EPR': 23, | |
209 | 'SYSTEM_EVENT': 24, | |
210 | } | |
211 | ||
212 | IOCTL_NUMBERS = { | |
213 | 'SET_FILTER': 0x40082406, | |
214 | 'ENABLE': 0x00002400, | |
215 | 'DISABLE': 0x00002401, | |
216 | 'RESET': 0x00002403, | |
217 | } | |
218 | ||
219 | class Arch(object): | |
220 | """Class that encapsulates global architecture specific data like | |
221 | syscall and ioctl numbers. | |
222 | ||
223 | """ | |
224 | @staticmethod | |
225 | def get_arch(): | |
226 | machine = os.uname()[4] | |
227 | ||
228 | if machine.startswith('ppc'): | |
229 | return ArchPPC() | |
230 | elif machine.startswith('aarch64'): | |
231 | return ArchA64() | |
232 | elif machine.startswith('s390'): | |
233 | return ArchS390() | |
234 | else: | |
235 | # X86_64 | |
236 | for line in open('/proc/cpuinfo'): | |
237 | if not line.startswith('flags'): | |
238 | continue | |
239 | ||
240 | flags = line.split() | |
241 | if 'vmx' in flags: | |
242 | return ArchX86(VMX_EXIT_REASONS) | |
243 | if 'svm' in flags: | |
244 | return ArchX86(SVM_EXIT_REASONS) | |
245 | return | |
246 | ||
247 | class ArchX86(Arch): | |
248 | def __init__(self, exit_reasons): | |
249 | self.sc_perf_evt_open = 298 | |
250 | self.ioctl_numbers = IOCTL_NUMBERS | |
251 | self.exit_reasons = exit_reasons | |
252 | ||
253 | class ArchPPC(Arch): | |
254 | def __init__(self): | |
255 | self.sc_perf_evt_open = 319 | |
256 | self.ioctl_numbers = IOCTL_NUMBERS | |
257 | self.ioctl_numbers['ENABLE'] = 0x20002400 | |
258 | self.ioctl_numbers['DISABLE'] = 0x20002401 | |
259 | ||
260 | # PPC comes in 32 and 64 bit and some generated ioctl | |
261 | # numbers depend on the wordsize. | |
262 | char_ptr_size = ctypes.sizeof(ctypes.c_char_p) | |
263 | self.ioctl_numbers['SET_FILTER'] = 0x80002406 | char_ptr_size << 16 | |
264 | ||
265 | class ArchA64(Arch): | |
266 | def __init__(self): | |
267 | self.sc_perf_evt_open = 241 | |
268 | self.ioctl_numbers = IOCTL_NUMBERS | |
269 | self.exit_reasons = AARCH64_EXIT_REASONS | |
270 | ||
271 | class ArchS390(Arch): | |
272 | def __init__(self): | |
273 | self.sc_perf_evt_open = 331 | |
274 | self.ioctl_numbers = IOCTL_NUMBERS | |
275 | self.exit_reasons = None | |
276 | ||
277 | ARCH = Arch.get_arch() | |
278 | ||
279 | ||
280 | def walkdir(path): | |
281 | """Returns os.walk() data for specified directory. | |
282 | ||
283 | As it is only a wrapper it returns the same 3-tuple of (dirpath, | |
284 | dirnames, filenames). | |
285 | """ | |
286 | return next(os.walk(path)) | |
287 | ||
288 | ||
289 | def parse_int_list(list_string): | |
290 | """Returns an int list from a string of comma separated integers and | |
291 | integer ranges.""" | |
292 | integers = [] | |
293 | members = list_string.split(',') | |
294 | ||
295 | for member in members: | |
296 | if '-' not in member: | |
297 | integers.append(int(member)) | |
298 | else: | |
299 | int_range = member.split('-') | |
300 | integers.extend(range(int(int_range[0]), | |
301 | int(int_range[1]) + 1)) | |
302 | ||
303 | return integers | |
304 | ||
305 | ||
306 | def get_online_cpus(): | |
307 | with open('/sys/devices/system/cpu/online') as cpu_list: | |
308 | cpu_string = cpu_list.readline() | |
309 | return parse_int_list(cpu_string) | |
310 | ||
311 | ||
312 | def get_filters(): | |
313 | filters = {} | |
314 | filters['kvm_userspace_exit'] = ('reason', USERSPACE_EXIT_REASONS) | |
315 | if ARCH.exit_reasons: | |
316 | filters['kvm_exit'] = ('exit_reason', ARCH.exit_reasons) | |
317 | return filters | |
318 | ||
319 | libc = ctypes.CDLL('libc.so.6', use_errno=True) | |
320 | syscall = libc.syscall | |
321 | ||
322 | class perf_event_attr(ctypes.Structure): | |
323 | _fields_ = [('type', ctypes.c_uint32), | |
324 | ('size', ctypes.c_uint32), | |
325 | ('config', ctypes.c_uint64), | |
326 | ('sample_freq', ctypes.c_uint64), | |
327 | ('sample_type', ctypes.c_uint64), | |
328 | ('read_format', ctypes.c_uint64), | |
329 | ('flags', ctypes.c_uint64), | |
330 | ('wakeup_events', ctypes.c_uint32), | |
331 | ('bp_type', ctypes.c_uint32), | |
332 | ('bp_addr', ctypes.c_uint64), | |
333 | ('bp_len', ctypes.c_uint64), | |
334 | ] | |
335 | ||
336 | def __init__(self): | |
337 | super(self.__class__, self).__init__() | |
338 | self.type = PERF_TYPE_TRACEPOINT | |
339 | self.size = ctypes.sizeof(self) | |
340 | self.read_format = PERF_FORMAT_GROUP | |
341 | ||
342 | def perf_event_open(attr, pid, cpu, group_fd, flags): | |
343 | return syscall(ARCH.sc_perf_evt_open, ctypes.pointer(attr), | |
344 | ctypes.c_int(pid), ctypes.c_int(cpu), | |
345 | ctypes.c_int(group_fd), ctypes.c_long(flags)) | |
346 | ||
347 | PERF_TYPE_TRACEPOINT = 2 | |
348 | PERF_FORMAT_GROUP = 1 << 3 | |
349 | ||
350 | PATH_DEBUGFS_TRACING = '/sys/kernel/debug/tracing' | |
351 | PATH_DEBUGFS_KVM = '/sys/kernel/debug/kvm' | |
352 | ||
353 | class Group(object): | |
354 | def __init__(self): | |
355 | self.events = [] | |
356 | ||
357 | def add_event(self, event): | |
358 | self.events.append(event) | |
359 | ||
360 | def read(self): | |
361 | length = 8 * (1 + len(self.events)) | |
362 | read_format = 'xxxxxxxx' + 'Q' * len(self.events) | |
363 | return dict(zip([event.name for event in self.events], | |
364 | struct.unpack(read_format, | |
365 | os.read(self.events[0].fd, length)))) | |
366 | ||
367 | class Event(object): | |
368 | def __init__(self, name, group, trace_cpu, trace_point, trace_filter, | |
369 | trace_set='kvm'): | |
370 | self.name = name | |
371 | self.fd = None | |
372 | self.setup_event(group, trace_cpu, trace_point, trace_filter, | |
373 | trace_set) | |
374 | ||
375 | def setup_event_attribute(self, trace_set, trace_point): | |
376 | id_path = os.path.join(PATH_DEBUGFS_TRACING, 'events', trace_set, | |
377 | trace_point, 'id') | |
378 | ||
379 | event_attr = perf_event_attr() | |
380 | event_attr.config = int(open(id_path).read()) | |
381 | return event_attr | |
382 | ||
383 | def setup_event(self, group, trace_cpu, trace_point, trace_filter, | |
384 | trace_set): | |
385 | event_attr = self.setup_event_attribute(trace_set, trace_point) | |
386 | ||
387 | group_leader = -1 | |
388 | if group.events: | |
389 | group_leader = group.events[0].fd | |
390 | ||
391 | fd = perf_event_open(event_attr, -1, trace_cpu, | |
392 | group_leader, 0) | |
393 | if fd == -1: | |
394 | err = ctypes.get_errno() | |
395 | raise OSError(err, os.strerror(err), | |
396 | 'while calling sys_perf_event_open().') | |
397 | ||
398 | if trace_filter: | |
399 | fcntl.ioctl(fd, ARCH.ioctl_numbers['SET_FILTER'], | |
400 | trace_filter) | |
401 | ||
402 | self.fd = fd | |
403 | ||
404 | def enable(self): | |
405 | fcntl.ioctl(self.fd, ARCH.ioctl_numbers['ENABLE'], 0) | |
406 | ||
407 | def disable(self): | |
408 | fcntl.ioctl(self.fd, ARCH.ioctl_numbers['DISABLE'], 0) | |
409 | ||
410 | def reset(self): | |
411 | fcntl.ioctl(self.fd, ARCH.ioctl_numbers['RESET'], 0) | |
412 | ||
413 | class TracepointProvider(object): | |
414 | def __init__(self): | |
415 | self.group_leaders = [] | |
416 | self.filters = get_filters() | |
417 | self._fields = self.get_available_fields() | |
418 | self.setup_traces() | |
419 | self.fields = self._fields | |
420 | ||
421 | def get_available_fields(self): | |
422 | path = os.path.join(PATH_DEBUGFS_TRACING, 'events', 'kvm') | |
423 | fields = walkdir(path)[1] | |
424 | extra = [] | |
425 | for field in fields: | |
426 | if field in self.filters: | |
427 | filter_name_, filter_dicts = self.filters[field] | |
428 | for name in filter_dicts: | |
429 | extra.append(field + '(' + name + ')') | |
430 | fields += extra | |
431 | return fields | |
432 | ||
433 | def setup_traces(self): | |
434 | cpus = get_online_cpus() | |
435 | ||
436 | # The constant is needed as a buffer for python libs, std | |
437 | # streams and other files that the script opens. | |
438 | newlim = len(cpus) * len(self._fields) + 50 | |
439 | try: | |
440 | softlim_, hardlim = resource.getrlimit(resource.RLIMIT_NOFILE) | |
441 | ||
442 | if hardlim < newlim: | |
443 | # Now we need CAP_SYS_RESOURCE, to increase the hard limit. | |
444 | resource.setrlimit(resource.RLIMIT_NOFILE, (newlim, newlim)) | |
445 | else: | |
446 | # Raising the soft limit is sufficient. | |
447 | resource.setrlimit(resource.RLIMIT_NOFILE, (newlim, hardlim)) | |
448 | ||
449 | except ValueError: | |
450 | sys.exit("NOFILE rlimit could not be raised to {0}".format(newlim)) | |
451 | ||
452 | for cpu in cpus: | |
453 | group = Group() | |
454 | for name in self._fields: | |
455 | tracepoint = name | |
456 | tracefilter = None | |
457 | match = re.match(r'(.*)\((.*)\)', name) | |
458 | if match: | |
459 | tracepoint, sub = match.groups() | |
460 | tracefilter = ('%s==%d\0' % | |
461 | (self.filters[tracepoint][0], | |
462 | self.filters[tracepoint][1][sub])) | |
463 | ||
464 | group.add_event(Event(name=name, | |
465 | group=group, | |
466 | trace_cpu=cpu, | |
467 | trace_point=tracepoint, | |
468 | trace_filter=tracefilter)) | |
469 | self.group_leaders.append(group) | |
470 | ||
471 | def available_fields(self): | |
472 | return self.get_available_fields() | |
473 | ||
474 | @property | |
475 | def fields(self): | |
476 | return self._fields | |
477 | ||
478 | @fields.setter | |
479 | def fields(self, fields): | |
480 | self._fields = fields | |
481 | for group in self.group_leaders: | |
482 | for index, event in enumerate(group.events): | |
483 | if event.name in fields: | |
484 | event.reset() | |
485 | event.enable() | |
486 | else: | |
487 | # Do not disable the group leader. | |
488 | # It would disable all of its events. | |
489 | if index != 0: | |
490 | event.disable() | |
491 | ||
492 | def read(self): | |
493 | ret = defaultdict(int) | |
494 | for group in self.group_leaders: | |
495 | for name, val in group.read().iteritems(): | |
496 | if name in self._fields: | |
497 | ret[name] += val | |
498 | return ret | |
499 | ||
500 | class DebugfsProvider(object): | |
501 | def __init__(self): | |
502 | self._fields = self.get_available_fields() | |
503 | ||
504 | def get_available_fields(self): | |
505 | return walkdir(PATH_DEBUGFS_KVM)[2] | |
506 | ||
507 | @property | |
508 | def fields(self): | |
509 | return self._fields | |
510 | ||
511 | @fields.setter | |
512 | def fields(self, fields): | |
513 | self._fields = fields | |
514 | ||
515 | def read(self): | |
516 | def val(key): | |
517 | return int(file(PATH_DEBUGFS_KVM + '/' + key).read()) | |
518 | return dict([(key, val(key)) for key in self._fields]) | |
519 | ||
520 | class Stats(object): | |
521 | def __init__(self, providers, fields=None): | |
522 | self.providers = providers | |
523 | self._fields_filter = fields | |
524 | self.values = {} | |
525 | self.update_provider_filters() | |
526 | ||
527 | def update_provider_filters(self): | |
528 | def wanted(key): | |
529 | if not self._fields_filter: | |
530 | return True | |
531 | return re.match(self._fields_filter, key) is not None | |
532 | ||
533 | # As we reset the counters when updating the fields we can | |
534 | # also clear the cache of old values. | |
535 | self.values = {} | |
536 | for provider in self.providers: | |
537 | provider_fields = [key for key in provider.get_available_fields() | |
538 | if wanted(key)] | |
539 | provider.fields = provider_fields | |
540 | ||
541 | @property | |
542 | def fields_filter(self): | |
543 | return self._fields_filter | |
544 | ||
545 | @fields_filter.setter | |
546 | def fields_filter(self, fields_filter): | |
547 | self._fields_filter = fields_filter | |
548 | self.update_provider_filters() | |
549 | ||
550 | def get(self): | |
551 | for provider in self.providers: | |
552 | new = provider.read() | |
553 | for key in provider.fields: | |
554 | oldval = self.values.get(key, (0, 0)) | |
555 | newval = new.get(key, 0) | |
556 | newdelta = None | |
557 | if oldval is not None: | |
558 | newdelta = newval - oldval[0] | |
559 | self.values[key] = (newval, newdelta) | |
560 | return self.values | |
561 | ||
562 | LABEL_WIDTH = 40 | |
563 | NUMBER_WIDTH = 10 | |
564 | ||
565 | class Tui(object): | |
566 | def __init__(self, stats): | |
567 | self.stats = stats | |
568 | self.screen = None | |
569 | self.drilldown = False | |
570 | self.update_drilldown() | |
571 | ||
572 | def __enter__(self): | |
573 | """Initialises curses for later use. Based on curses.wrapper | |
574 | implementation from the Python standard library.""" | |
575 | self.screen = curses.initscr() | |
576 | curses.noecho() | |
577 | curses.cbreak() | |
578 | ||
579 | # The try/catch works around a minor bit of | |
580 | # over-conscientiousness in the curses module, the error | |
581 | # return from C start_color() is ignorable. | |
582 | try: | |
583 | curses.start_color() | |
584 | except: | |
585 | pass | |
586 | ||
587 | curses.use_default_colors() | |
588 | return self | |
589 | ||
590 | def __exit__(self, *exception): | |
591 | """Resets the terminal to its normal state. Based on curses.wrappre | |
592 | implementation from the Python standard library.""" | |
593 | if self.screen: | |
594 | self.screen.keypad(0) | |
595 | curses.echo() | |
596 | curses.nocbreak() | |
597 | curses.endwin() | |
598 | ||
599 | def update_drilldown(self): | |
600 | if not self.stats.fields_filter: | |
601 | self.stats.fields_filter = r'^[^\(]*$' | |
602 | ||
603 | elif self.stats.fields_filter == r'^[^\(]*$': | |
604 | self.stats.fields_filter = None | |
605 | ||
606 | def refresh(self, sleeptime): | |
607 | self.screen.erase() | |
608 | self.screen.addstr(0, 0, 'kvm statistics - summary', curses.A_BOLD) | |
609 | self.screen.addstr(2, 1, 'Event') | |
610 | self.screen.addstr(2, 1 + LABEL_WIDTH + NUMBER_WIDTH - | |
611 | len('Total'), 'Total') | |
612 | self.screen.addstr(2, 1 + LABEL_WIDTH + NUMBER_WIDTH + 8 - | |
613 | len('Current'), 'Current') | |
614 | row = 3 | |
615 | stats = self.stats.get() | |
616 | def sortkey(x): | |
617 | if stats[x][1]: | |
618 | return (-stats[x][1], -stats[x][0]) | |
619 | else: | |
620 | return (0, -stats[x][0]) | |
621 | for key in sorted(stats.keys(), key=sortkey): | |
622 | ||
623 | if row >= self.screen.getmaxyx()[0]: | |
624 | break | |
625 | values = stats[key] | |
626 | if not values[0] and not values[1]: | |
627 | break | |
628 | col = 1 | |
629 | self.screen.addstr(row, col, key) | |
630 | col += LABEL_WIDTH | |
631 | self.screen.addstr(row, col, '%10d' % (values[0],)) | |
632 | col += NUMBER_WIDTH | |
633 | if values[1] is not None: | |
634 | self.screen.addstr(row, col, '%8d' % (values[1] / sleeptime,)) | |
635 | row += 1 | |
636 | self.screen.refresh() | |
637 | ||
638 | def show_filter_selection(self): | |
639 | while True: | |
640 | self.screen.erase() | |
641 | self.screen.addstr(0, 0, | |
642 | "Show statistics for events matching a regex.", | |
643 | curses.A_BOLD) | |
644 | self.screen.addstr(2, 0, | |
645 | "Current regex: {0}" | |
646 | .format(self.stats.fields_filter)) | |
647 | self.screen.addstr(3, 0, "New regex: ") | |
648 | curses.echo() | |
649 | regex = self.screen.getstr() | |
650 | curses.noecho() | |
651 | if len(regex) == 0: | |
652 | return | |
653 | try: | |
654 | re.compile(regex) | |
655 | self.stats.fields_filter = regex | |
656 | return | |
657 | except re.error: | |
658 | continue | |
659 | ||
660 | def show_stats(self): | |
661 | sleeptime = 0.25 | |
662 | while True: | |
663 | self.refresh(sleeptime) | |
664 | curses.halfdelay(int(sleeptime * 10)) | |
665 | sleeptime = 3 | |
666 | try: | |
667 | char = self.screen.getkey() | |
668 | if char == 'x': | |
669 | self.drilldown = not self.drilldown | |
670 | self.update_drilldown() | |
671 | if char == 'q': | |
672 | break | |
673 | if char == 'f': | |
674 | self.show_filter_selection() | |
675 | except KeyboardInterrupt: | |
676 | break | |
677 | except curses.error: | |
678 | continue | |
679 | ||
680 | def batch(stats): | |
681 | s = stats.get() | |
682 | time.sleep(1) | |
683 | s = stats.get() | |
684 | for key in sorted(s.keys()): | |
685 | values = s[key] | |
686 | print '%-42s%10d%10d' % (key, values[0], values[1]) | |
687 | ||
688 | def log(stats): | |
689 | keys = sorted(stats.get().iterkeys()) | |
690 | def banner(): | |
691 | for k in keys: | |
692 | print '%s' % k, | |
693 | ||
694 | def statline(): | |
695 | s = stats.get() | |
696 | for k in keys: | |
697 | print ' %9d' % s[k][1], | |
698 | ||
699 | line = 0 | |
700 | banner_repeat = 20 | |
701 | while True: | |
702 | time.sleep(1) | |
703 | if line % banner_repeat == 0: | |
704 | banner() | |
705 | statline() | |
706 | line += 1 | |
707 | ||
708 | def get_options(): | |
709 | description_text = """ | |
710 | This script displays various statistics about VMs running under KVM. | |
711 | The statistics are gathered from the KVM debugfs entries and / or the | |
712 | currently available perf traces. | |
713 | ||
714 | The monitoring takes additional cpu cycles and might affect the VM's | |
715 | performance. | |
716 | ||
717 | Requirements: | |
718 | - Access to: | |
719 | /sys/kernel/debug/kvm | |
720 | /sys/kernel/debug/trace/events/* | |
721 | /proc/pid/task | |
722 | - /proc/sys/kernel/perf_event_paranoid < 1 if user has no | |
723 | CAP_SYS_ADMIN and perf events are used. | |
724 | - CAP_SYS_RESOURCE if the hard limit is not high enough to allow | |
725 | the large number of files that are possibly opened. | |
726 | """ | |
727 | ||
728 | class PlainHelpFormatter(optparse.IndentedHelpFormatter): | |
729 | def format_description(self, description): | |
730 | if description: | |
731 | return description + "\n" | |
732 | else: | |
733 | return "" | |
734 | ||
735 | optparser = optparse.OptionParser(description=description_text, | |
736 | formatter=PlainHelpFormatter()) | |
737 | optparser.add_option('-1', '--once', '--batch', | |
738 | action='store_true', | |
739 | default=False, | |
740 | dest='once', | |
741 | help='run in batch mode for one second', | |
742 | ) | |
743 | optparser.add_option('-l', '--log', | |
744 | action='store_true', | |
745 | default=False, | |
746 | dest='log', | |
747 | help='run in logging mode (like vmstat)', | |
748 | ) | |
749 | optparser.add_option('-t', '--tracepoints', | |
750 | action='store_true', | |
751 | default=False, | |
752 | dest='tracepoints', | |
753 | help='retrieve statistics from tracepoints', | |
754 | ) | |
755 | optparser.add_option('-d', '--debugfs', | |
756 | action='store_true', | |
757 | default=False, | |
758 | dest='debugfs', | |
759 | help='retrieve statistics from debugfs', | |
760 | ) | |
761 | optparser.add_option('-f', '--fields', | |
762 | action='store', | |
763 | default=None, | |
764 | dest='fields', | |
765 | help='fields to display (regex)', | |
766 | ) | |
767 | (options, _) = optparser.parse_args(sys.argv) | |
768 | return options | |
769 | ||
770 | def get_providers(options): | |
771 | providers = [] | |
772 | ||
773 | if options.tracepoints: | |
774 | providers.append(TracepointProvider()) | |
775 | if options.debugfs: | |
776 | providers.append(DebugfsProvider()) | |
777 | if len(providers) == 0: | |
778 | providers.append(TracepointProvider()) | |
779 | ||
780 | return providers | |
781 | ||
782 | def check_access(options): | |
783 | if not os.path.exists('/sys/kernel/debug'): | |
784 | sys.stderr.write('Please enable CONFIG_DEBUG_FS in your kernel.') | |
785 | sys.exit(1) | |
786 | ||
787 | if not os.path.exists(PATH_DEBUGFS_KVM): | |
788 | sys.stderr.write("Please make sure, that debugfs is mounted and " | |
789 | "readable by the current user:\n" | |
790 | "('mount -t debugfs debugfs /sys/kernel/debug')\n" | |
791 | "Also ensure, that the kvm modules are loaded.\n") | |
792 | sys.exit(1) | |
793 | ||
794 | if not os.path.exists(PATH_DEBUGFS_TRACING) and (options.tracepoints | |
795 | or not options.debugfs): | |
796 | sys.stderr.write("Please enable CONFIG_TRACING in your kernel " | |
797 | "when using the option -t (default).\n" | |
798 | "If it is enabled, make {0} readable by the " | |
799 | "current user.\n" | |
800 | .format(PATH_DEBUGFS_TRACING)) | |
801 | if options.tracepoints: | |
802 | sys.exit(1) | |
803 | ||
804 | sys.stderr.write("Falling back to debugfs statistics!\n") | |
805 | options.debugfs = True | |
806 | sleep(5) | |
807 | ||
808 | return options | |
809 | ||
810 | def main(): | |
811 | options = get_options() | |
812 | options = check_access(options) | |
813 | providers = get_providers(options) | |
814 | stats = Stats(providers, fields=options.fields) | |
815 | ||
816 | if options.log: | |
817 | log(stats) | |
818 | elif not options.once: | |
819 | with Tui(stats) as tui: | |
820 | tui.show_stats() | |
821 | else: | |
822 | batch(stats) | |
823 | ||
824 | if __name__ == "__main__": | |
825 | main() |