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