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