diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2018-02-26 12:28:35 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2018-02-26 12:28:35 -0500 |
commit | d4858aaf6bd8a90e2dacc0dfec2077e334dcedbf (patch) | |
tree | ccb1934d3943fd7ed443f533409d02f4e2a5ad05 /tools | |
parent | 4a3928c6f8a53fa1aed28ccba227742486e8ddcb (diff) | |
parent | 9c5e0afaf15788bcbd1c3469da701ac3da826886 (diff) |
Merge tag 'for-linus' of git://git.kernel.org/pub/scm/virt/kvm/kvm
Pull KVM fixes from Paolo Bonzini:
"s390:
- optimization for the exitless interrupt support that was merged in 4.16-rc1
- improve the branch prediction blocking for nested KVM
- replace some jump tables with switch statements to improve expoline performance
- fixes for multiple epoch facility
ARM:
- fix the interaction of userspace irqchip VMs with in-kernel irqchip VMs
- make sure we can build 32-bit KVM/ARM with gcc-8.
x86:
- fixes for AMD SEV
- fixes for Intel nested VMX, emulated UMIP and a dump_stack() on VM startup
- fixes for async page fault migration
- small optimization to PV TLB flush (new in 4.16-rc1)
- syzkaller fixes
Generic:
- compiler warning fixes
- syzkaller fixes
- more improvements to the kvm_stat tool
Two more small Spectre fixes are going to reach you via Ingo"
* tag 'for-linus' of git://git.kernel.org/pub/scm/virt/kvm/kvm: (40 commits)
KVM: SVM: Fix SEV LAUNCH_SECRET command
KVM: SVM: install RSM intercept
KVM: SVM: no need to call access_ok() in LAUNCH_MEASURE command
include: psp-sev: Capitalize invalid length enum
crypto: ccp: Fix sparse, use plain integer as NULL pointer
KVM: X86: Avoid traversing all the cpus for pv tlb flush when steal time is disabled
x86/kvm: Make parse_no_xxx __init for kvm
KVM: x86: fix backward migration with async_PF
kvm: fix warning for non-x86 builds
kvm: fix warning for CONFIG_HAVE_KVM_EVENTFD builds
tools/kvm_stat: print 'Total' line for multiple events only
tools/kvm_stat: group child events indented after parent
tools/kvm_stat: separate drilldown and fields filtering
tools/kvm_stat: eliminate extra guest/pid selection dialog
tools/kvm_stat: mark private methods as such
tools/kvm_stat: fix debugfs handling
tools/kvm_stat: print error on invalid regex
tools/kvm_stat: fix crash when filtering out all non-child trace events
tools/kvm_stat: avoid 'is' for equality checks
tools/kvm_stat: use a more pythonic way to iterate over dictionaries
...
Diffstat (limited to 'tools')
-rwxr-xr-x | tools/kvm/kvm_stat/kvm_stat | 503 | ||||
-rw-r--r-- | tools/kvm/kvm_stat/kvm_stat.txt | 4 |
2 files changed, 287 insertions, 220 deletions
diff --git a/tools/kvm/kvm_stat/kvm_stat b/tools/kvm/kvm_stat/kvm_stat index a5684d0968b4..5898c22ba310 100755 --- a/tools/kvm/kvm_stat/kvm_stat +++ b/tools/kvm/kvm_stat/kvm_stat | |||
@@ -33,7 +33,7 @@ import resource | |||
33 | import struct | 33 | import struct |
34 | import re | 34 | import re |
35 | import subprocess | 35 | import subprocess |
36 | from collections import defaultdict | 36 | from collections import defaultdict, namedtuple |
37 | 37 | ||
38 | VMX_EXIT_REASONS = { | 38 | VMX_EXIT_REASONS = { |
39 | 'EXCEPTION_NMI': 0, | 39 | 'EXCEPTION_NMI': 0, |
@@ -228,6 +228,7 @@ IOCTL_NUMBERS = { | |||
228 | } | 228 | } |
229 | 229 | ||
230 | ENCODING = locale.getpreferredencoding(False) | 230 | ENCODING = locale.getpreferredencoding(False) |
231 | TRACE_FILTER = re.compile(r'^[^\(]*$') | ||
231 | 232 | ||
232 | 233 | ||
233 | class Arch(object): | 234 | class Arch(object): |
@@ -260,6 +261,11 @@ class Arch(object): | |||
260 | return ArchX86(SVM_EXIT_REASONS) | 261 | return ArchX86(SVM_EXIT_REASONS) |
261 | return | 262 | return |
262 | 263 | ||
264 | def tracepoint_is_child(self, field): | ||
265 | if (TRACE_FILTER.match(field)): | ||
266 | return None | ||
267 | return field.split('(', 1)[0] | ||
268 | |||
263 | 269 | ||
264 | class ArchX86(Arch): | 270 | class ArchX86(Arch): |
265 | def __init__(self, exit_reasons): | 271 | def __init__(self, exit_reasons): |
@@ -267,6 +273,10 @@ class ArchX86(Arch): | |||
267 | self.ioctl_numbers = IOCTL_NUMBERS | 273 | self.ioctl_numbers = IOCTL_NUMBERS |
268 | self.exit_reasons = exit_reasons | 274 | self.exit_reasons = exit_reasons |
269 | 275 | ||
276 | def debugfs_is_child(self, field): | ||
277 | """ Returns name of parent if 'field' is a child, None otherwise """ | ||
278 | return None | ||
279 | |||
270 | 280 | ||
271 | class ArchPPC(Arch): | 281 | class ArchPPC(Arch): |
272 | def __init__(self): | 282 | def __init__(self): |
@@ -282,6 +292,10 @@ class ArchPPC(Arch): | |||
282 | self.ioctl_numbers['SET_FILTER'] = 0x80002406 | char_ptr_size << 16 | 292 | self.ioctl_numbers['SET_FILTER'] = 0x80002406 | char_ptr_size << 16 |
283 | self.exit_reasons = {} | 293 | self.exit_reasons = {} |
284 | 294 | ||
295 | def debugfs_is_child(self, field): | ||
296 | """ Returns name of parent if 'field' is a child, None otherwise """ | ||
297 | return None | ||
298 | |||
285 | 299 | ||
286 | class ArchA64(Arch): | 300 | class ArchA64(Arch): |
287 | def __init__(self): | 301 | def __init__(self): |
@@ -289,6 +303,10 @@ class ArchA64(Arch): | |||
289 | self.ioctl_numbers = IOCTL_NUMBERS | 303 | self.ioctl_numbers = IOCTL_NUMBERS |
290 | self.exit_reasons = AARCH64_EXIT_REASONS | 304 | self.exit_reasons = AARCH64_EXIT_REASONS |
291 | 305 | ||
306 | def debugfs_is_child(self, field): | ||
307 | """ Returns name of parent if 'field' is a child, None otherwise """ | ||
308 | return None | ||
309 | |||
292 | 310 | ||
293 | class ArchS390(Arch): | 311 | class ArchS390(Arch): |
294 | def __init__(self): | 312 | def __init__(self): |
@@ -296,6 +314,12 @@ class ArchS390(Arch): | |||
296 | self.ioctl_numbers = IOCTL_NUMBERS | 314 | self.ioctl_numbers = IOCTL_NUMBERS |
297 | self.exit_reasons = None | 315 | self.exit_reasons = None |
298 | 316 | ||
317 | def debugfs_is_child(self, field): | ||
318 | """ Returns name of parent if 'field' is a child, None otherwise """ | ||
319 | if field.startswith('instruction_'): | ||
320 | return 'exit_instruction' | ||
321 | |||
322 | |||
299 | ARCH = Arch.get_arch() | 323 | ARCH = Arch.get_arch() |
300 | 324 | ||
301 | 325 | ||
@@ -331,9 +355,6 @@ class perf_event_attr(ctypes.Structure): | |||
331 | PERF_TYPE_TRACEPOINT = 2 | 355 | PERF_TYPE_TRACEPOINT = 2 |
332 | PERF_FORMAT_GROUP = 1 << 3 | 356 | PERF_FORMAT_GROUP = 1 << 3 |
333 | 357 | ||
334 | PATH_DEBUGFS_TRACING = '/sys/kernel/debug/tracing' | ||
335 | PATH_DEBUGFS_KVM = '/sys/kernel/debug/kvm' | ||
336 | |||
337 | 358 | ||
338 | class Group(object): | 359 | class Group(object): |
339 | """Represents a perf event group.""" | 360 | """Represents a perf event group.""" |
@@ -376,8 +397,8 @@ class Event(object): | |||
376 | self.syscall = self.libc.syscall | 397 | self.syscall = self.libc.syscall |
377 | self.name = name | 398 | self.name = name |
378 | self.fd = None | 399 | self.fd = None |
379 | self.setup_event(group, trace_cpu, trace_pid, trace_point, | 400 | self._setup_event(group, trace_cpu, trace_pid, trace_point, |
380 | trace_filter, trace_set) | 401 | trace_filter, trace_set) |
381 | 402 | ||
382 | def __del__(self): | 403 | def __del__(self): |
383 | """Closes the event's file descriptor. | 404 | """Closes the event's file descriptor. |
@@ -390,7 +411,7 @@ class Event(object): | |||
390 | if self.fd: | 411 | if self.fd: |
391 | os.close(self.fd) | 412 | os.close(self.fd) |
392 | 413 | ||
393 | def perf_event_open(self, attr, pid, cpu, group_fd, flags): | 414 | def _perf_event_open(self, attr, pid, cpu, group_fd, flags): |
394 | """Wrapper for the sys_perf_evt_open() syscall. | 415 | """Wrapper for the sys_perf_evt_open() syscall. |
395 | 416 | ||
396 | Used to set up performance events, returns a file descriptor or -1 | 417 | Used to set up performance events, returns a file descriptor or -1 |
@@ -409,7 +430,7 @@ class Event(object): | |||
409 | ctypes.c_int(pid), ctypes.c_int(cpu), | 430 | ctypes.c_int(pid), ctypes.c_int(cpu), |
410 | ctypes.c_int(group_fd), ctypes.c_long(flags)) | 431 | ctypes.c_int(group_fd), ctypes.c_long(flags)) |
411 | 432 | ||
412 | def setup_event_attribute(self, trace_set, trace_point): | 433 | def _setup_event_attribute(self, trace_set, trace_point): |
413 | """Returns an initialized ctype perf_event_attr struct.""" | 434 | """Returns an initialized ctype perf_event_attr struct.""" |
414 | 435 | ||
415 | id_path = os.path.join(PATH_DEBUGFS_TRACING, 'events', trace_set, | 436 | id_path = os.path.join(PATH_DEBUGFS_TRACING, 'events', trace_set, |
@@ -419,8 +440,8 @@ class Event(object): | |||
419 | event_attr.config = int(open(id_path).read()) | 440 | event_attr.config = int(open(id_path).read()) |
420 | return event_attr | 441 | return event_attr |
421 | 442 | ||
422 | def setup_event(self, group, trace_cpu, trace_pid, trace_point, | 443 | def _setup_event(self, group, trace_cpu, trace_pid, trace_point, |
423 | trace_filter, trace_set): | 444 | trace_filter, trace_set): |
424 | """Sets up the perf event in Linux. | 445 | """Sets up the perf event in Linux. |
425 | 446 | ||
426 | Issues the syscall to register the event in the kernel and | 447 | Issues the syscall to register the event in the kernel and |
@@ -428,7 +449,7 @@ class Event(object): | |||
428 | 449 | ||
429 | """ | 450 | """ |
430 | 451 | ||
431 | event_attr = self.setup_event_attribute(trace_set, trace_point) | 452 | event_attr = self._setup_event_attribute(trace_set, trace_point) |
432 | 453 | ||
433 | # First event will be group leader. | 454 | # First event will be group leader. |
434 | group_leader = -1 | 455 | group_leader = -1 |
@@ -437,8 +458,8 @@ class Event(object): | |||
437 | if group.events: | 458 | if group.events: |
438 | group_leader = group.events[0].fd | 459 | group_leader = group.events[0].fd |
439 | 460 | ||
440 | fd = self.perf_event_open(event_attr, trace_pid, | 461 | fd = self._perf_event_open(event_attr, trace_pid, |
441 | trace_cpu, group_leader, 0) | 462 | trace_cpu, group_leader, 0) |
442 | if fd == -1: | 463 | if fd == -1: |
443 | err = ctypes.get_errno() | 464 | err = ctypes.get_errno() |
444 | raise OSError(err, os.strerror(err), | 465 | raise OSError(err, os.strerror(err), |
@@ -475,6 +496,10 @@ class Event(object): | |||
475 | 496 | ||
476 | class Provider(object): | 497 | class Provider(object): |
477 | """Encapsulates functionalities used by all providers.""" | 498 | """Encapsulates functionalities used by all providers.""" |
499 | def __init__(self, pid): | ||
500 | self.child_events = False | ||
501 | self.pid = pid | ||
502 | |||
478 | @staticmethod | 503 | @staticmethod |
479 | def is_field_wanted(fields_filter, field): | 504 | def is_field_wanted(fields_filter, field): |
480 | """Indicate whether field is valid according to fields_filter.""" | 505 | """Indicate whether field is valid according to fields_filter.""" |
@@ -500,12 +525,12 @@ class TracepointProvider(Provider): | |||
500 | """ | 525 | """ |
501 | def __init__(self, pid, fields_filter): | 526 | def __init__(self, pid, fields_filter): |
502 | self.group_leaders = [] | 527 | self.group_leaders = [] |
503 | self.filters = self.get_filters() | 528 | self.filters = self._get_filters() |
504 | self.update_fields(fields_filter) | 529 | self.update_fields(fields_filter) |
505 | self.pid = pid | 530 | super(TracepointProvider, self).__init__(pid) |
506 | 531 | ||
507 | @staticmethod | 532 | @staticmethod |
508 | def get_filters(): | 533 | def _get_filters(): |
509 | """Returns a dict of trace events, their filter ids and | 534 | """Returns a dict of trace events, their filter ids and |
510 | the values that can be filtered. | 535 | the values that can be filtered. |
511 | 536 | ||
@@ -521,8 +546,8 @@ class TracepointProvider(Provider): | |||
521 | filters['kvm_exit'] = ('exit_reason', ARCH.exit_reasons) | 546 | filters['kvm_exit'] = ('exit_reason', ARCH.exit_reasons) |
522 | return filters | 547 | return filters |
523 | 548 | ||
524 | def get_available_fields(self): | 549 | def _get_available_fields(self): |
525 | """Returns a list of available event's of format 'event name(filter | 550 | """Returns a list of available events of format 'event name(filter |
526 | name)'. | 551 | name)'. |
527 | 552 | ||
528 | All available events have directories under | 553 | All available events have directories under |
@@ -549,11 +574,12 @@ class TracepointProvider(Provider): | |||
549 | 574 | ||
550 | def update_fields(self, fields_filter): | 575 | def update_fields(self, fields_filter): |
551 | """Refresh fields, applying fields_filter""" | 576 | """Refresh fields, applying fields_filter""" |
552 | self.fields = [field for field in self.get_available_fields() | 577 | self.fields = [field for field in self._get_available_fields() |
553 | if self.is_field_wanted(fields_filter, field)] | 578 | if self.is_field_wanted(fields_filter, field) or |
579 | ARCH.tracepoint_is_child(field)] | ||
554 | 580 | ||
555 | @staticmethod | 581 | @staticmethod |
556 | def get_online_cpus(): | 582 | def _get_online_cpus(): |
557 | """Returns a list of cpu id integers.""" | 583 | """Returns a list of cpu id integers.""" |
558 | def parse_int_list(list_string): | 584 | def parse_int_list(list_string): |
559 | """Returns an int list from a string of comma separated integers and | 585 | """Returns an int list from a string of comma separated integers and |
@@ -575,17 +601,17 @@ class TracepointProvider(Provider): | |||
575 | cpu_string = cpu_list.readline() | 601 | cpu_string = cpu_list.readline() |
576 | return parse_int_list(cpu_string) | 602 | return parse_int_list(cpu_string) |
577 | 603 | ||
578 | def setup_traces(self): | 604 | def _setup_traces(self): |
579 | """Creates all event and group objects needed to be able to retrieve | 605 | """Creates all event and group objects needed to be able to retrieve |
580 | data.""" | 606 | data.""" |
581 | fields = self.get_available_fields() | 607 | fields = self._get_available_fields() |
582 | if self._pid > 0: | 608 | if self._pid > 0: |
583 | # Fetch list of all threads of the monitored pid, as qemu | 609 | # Fetch list of all threads of the monitored pid, as qemu |
584 | # starts a thread for each vcpu. | 610 | # starts a thread for each vcpu. |
585 | path = os.path.join('/proc', str(self._pid), 'task') | 611 | path = os.path.join('/proc', str(self._pid), 'task') |
586 | groupids = self.walkdir(path)[1] | 612 | groupids = self.walkdir(path)[1] |
587 | else: | 613 | else: |
588 | groupids = self.get_online_cpus() | 614 | groupids = self._get_online_cpus() |
589 | 615 | ||
590 | # The constant is needed as a buffer for python libs, std | 616 | # The constant is needed as a buffer for python libs, std |
591 | # streams and other files that the script opens. | 617 | # streams and other files that the script opens. |
@@ -663,7 +689,7 @@ class TracepointProvider(Provider): | |||
663 | # The garbage collector will get rid of all Event/Group | 689 | # The garbage collector will get rid of all Event/Group |
664 | # objects and open files after removing the references. | 690 | # objects and open files after removing the references. |
665 | self.group_leaders = [] | 691 | self.group_leaders = [] |
666 | self.setup_traces() | 692 | self._setup_traces() |
667 | self.fields = self._fields | 693 | self.fields = self._fields |
668 | 694 | ||
669 | def read(self, by_guest=0): | 695 | def read(self, by_guest=0): |
@@ -671,8 +697,12 @@ class TracepointProvider(Provider): | |||
671 | ret = defaultdict(int) | 697 | ret = defaultdict(int) |
672 | for group in self.group_leaders: | 698 | for group in self.group_leaders: |
673 | for name, val in group.read().items(): | 699 | for name, val in group.read().items(): |
674 | if name in self._fields: | 700 | if name not in self._fields: |
675 | ret[name] += val | 701 | continue |
702 | parent = ARCH.tracepoint_is_child(name) | ||
703 | if parent: | ||
704 | name += ' ' + parent | ||
705 | ret[name] += val | ||
676 | return ret | 706 | return ret |
677 | 707 | ||
678 | def reset(self): | 708 | def reset(self): |
@@ -690,11 +720,11 @@ class DebugfsProvider(Provider): | |||
690 | self._baseline = {} | 720 | self._baseline = {} |
691 | self.do_read = True | 721 | self.do_read = True |
692 | self.paths = [] | 722 | self.paths = [] |
693 | self.pid = pid | 723 | super(DebugfsProvider, self).__init__(pid) |
694 | if include_past: | 724 | if include_past: |
695 | self.restore() | 725 | self._restore() |
696 | 726 | ||
697 | def get_available_fields(self): | 727 | def _get_available_fields(self): |
698 | """"Returns a list of available fields. | 728 | """"Returns a list of available fields. |
699 | 729 | ||
700 | The fields are all available KVM debugfs files | 730 | The fields are all available KVM debugfs files |
@@ -704,8 +734,9 @@ class DebugfsProvider(Provider): | |||
704 | 734 | ||
705 | def update_fields(self, fields_filter): | 735 | def update_fields(self, fields_filter): |
706 | """Refresh fields, applying fields_filter""" | 736 | """Refresh fields, applying fields_filter""" |
707 | self._fields = [field for field in self.get_available_fields() | 737 | self._fields = [field for field in self._get_available_fields() |
708 | if self.is_field_wanted(fields_filter, field)] | 738 | if self.is_field_wanted(fields_filter, field) or |
739 | ARCH.debugfs_is_child(field)] | ||
709 | 740 | ||
710 | @property | 741 | @property |
711 | def fields(self): | 742 | def fields(self): |
@@ -758,7 +789,7 @@ class DebugfsProvider(Provider): | |||
758 | paths.append(dir) | 789 | paths.append(dir) |
759 | for path in paths: | 790 | for path in paths: |
760 | for field in self._fields: | 791 | for field in self._fields: |
761 | value = self.read_field(field, path) | 792 | value = self._read_field(field, path) |
762 | key = path + field | 793 | key = path + field |
763 | if reset == 1: | 794 | if reset == 1: |
764 | self._baseline[key] = value | 795 | self._baseline[key] = value |
@@ -766,20 +797,21 @@ class DebugfsProvider(Provider): | |||
766 | self._baseline[key] = 0 | 797 | self._baseline[key] = 0 |
767 | if self._baseline.get(key, -1) == -1: | 798 | if self._baseline.get(key, -1) == -1: |
768 | self._baseline[key] = value | 799 | self._baseline[key] = value |
769 | increment = (results.get(field, 0) + value - | 800 | parent = ARCH.debugfs_is_child(field) |
770 | self._baseline.get(key, 0)) | 801 | if parent: |
771 | if by_guest: | 802 | field = field + ' ' + parent |
772 | pid = key.split('-')[0] | 803 | else: |
773 | if pid in results: | 804 | if by_guest: |
774 | results[pid] += increment | 805 | field = key.split('-')[0] # set 'field' to 'pid' |
775 | else: | 806 | increment = value - self._baseline.get(key, 0) |
776 | results[pid] = increment | 807 | if field in results: |
808 | results[field] += increment | ||
777 | else: | 809 | else: |
778 | results[field] = increment | 810 | results[field] = increment |
779 | 811 | ||
780 | return results | 812 | return results |
781 | 813 | ||
782 | def read_field(self, field, path): | 814 | def _read_field(self, field, path): |
783 | """Returns the value of a single field from a specific VM.""" | 815 | """Returns the value of a single field from a specific VM.""" |
784 | try: | 816 | try: |
785 | return int(open(os.path.join(PATH_DEBUGFS_KVM, | 817 | return int(open(os.path.join(PATH_DEBUGFS_KVM, |
@@ -794,12 +826,15 @@ class DebugfsProvider(Provider): | |||
794 | self._baseline = {} | 826 | self._baseline = {} |
795 | self.read(1) | 827 | self.read(1) |
796 | 828 | ||
797 | def restore(self): | 829 | def _restore(self): |
798 | """Reset field counters""" | 830 | """Reset field counters""" |
799 | self._baseline = {} | 831 | self._baseline = {} |
800 | self.read(2) | 832 | self.read(2) |
801 | 833 | ||
802 | 834 | ||
835 | EventStat = namedtuple('EventStat', ['value', 'delta']) | ||
836 | |||
837 | |||
803 | class Stats(object): | 838 | class Stats(object): |
804 | """Manages the data providers and the data they provide. | 839 | """Manages the data providers and the data they provide. |
805 | 840 | ||
@@ -808,13 +843,13 @@ class Stats(object): | |||
808 | 843 | ||
809 | """ | 844 | """ |
810 | def __init__(self, options): | 845 | def __init__(self, options): |
811 | self.providers = self.get_providers(options) | 846 | self.providers = self._get_providers(options) |
812 | self._pid_filter = options.pid | 847 | self._pid_filter = options.pid |
813 | self._fields_filter = options.fields | 848 | self._fields_filter = options.fields |
814 | self.values = {} | 849 | self.values = {} |
850 | self._child_events = False | ||
815 | 851 | ||
816 | @staticmethod | 852 | def _get_providers(self, options): |
817 | def get_providers(options): | ||
818 | """Returns a list of data providers depending on the passed options.""" | 853 | """Returns a list of data providers depending on the passed options.""" |
819 | providers = [] | 854 | providers = [] |
820 | 855 | ||
@@ -826,7 +861,7 @@ class Stats(object): | |||
826 | 861 | ||
827 | return providers | 862 | return providers |
828 | 863 | ||
829 | def update_provider_filters(self): | 864 | def _update_provider_filters(self): |
830 | """Propagates fields filters to providers.""" | 865 | """Propagates fields filters to providers.""" |
831 | # As we reset the counters when updating the fields we can | 866 | # As we reset the counters when updating the fields we can |
832 | # also clear the cache of old values. | 867 | # also clear the cache of old values. |
@@ -847,7 +882,7 @@ class Stats(object): | |||
847 | def fields_filter(self, fields_filter): | 882 | def fields_filter(self, fields_filter): |
848 | if fields_filter != self._fields_filter: | 883 | if fields_filter != self._fields_filter: |
849 | self._fields_filter = fields_filter | 884 | self._fields_filter = fields_filter |
850 | self.update_provider_filters() | 885 | self._update_provider_filters() |
851 | 886 | ||
852 | @property | 887 | @property |
853 | def pid_filter(self): | 888 | def pid_filter(self): |
@@ -861,16 +896,33 @@ class Stats(object): | |||
861 | for provider in self.providers: | 896 | for provider in self.providers: |
862 | provider.pid = self._pid_filter | 897 | provider.pid = self._pid_filter |
863 | 898 | ||
899 | @property | ||
900 | def child_events(self): | ||
901 | return self._child_events | ||
902 | |||
903 | @child_events.setter | ||
904 | def child_events(self, val): | ||
905 | self._child_events = val | ||
906 | for provider in self.providers: | ||
907 | provider.child_events = val | ||
908 | |||
864 | def get(self, by_guest=0): | 909 | def get(self, by_guest=0): |
865 | """Returns a dict with field -> (value, delta to last value) of all | 910 | """Returns a dict with field -> (value, delta to last value) of all |
866 | provider data.""" | 911 | provider data. |
912 | Key formats: | ||
913 | * plain: 'key' is event name | ||
914 | * child-parent: 'key' is in format '<child> <parent>' | ||
915 | * pid: 'key' is the pid of the guest, and the record contains the | ||
916 | aggregated event data | ||
917 | These formats are generated by the providers, and handled in class TUI. | ||
918 | """ | ||
867 | for provider in self.providers: | 919 | for provider in self.providers: |
868 | new = provider.read(by_guest=by_guest) | 920 | new = provider.read(by_guest=by_guest) |
869 | for key in new if by_guest else provider.fields: | 921 | for key in new: |
870 | oldval = self.values.get(key, (0, 0))[0] | 922 | oldval = self.values.get(key, EventStat(0, 0)).value |
871 | newval = new.get(key, 0) | 923 | newval = new.get(key, 0) |
872 | newdelta = newval - oldval | 924 | newdelta = newval - oldval |
873 | self.values[key] = (newval, newdelta) | 925 | self.values[key] = EventStat(newval, newdelta) |
874 | return self.values | 926 | return self.values |
875 | 927 | ||
876 | def toggle_display_guests(self, to_pid): | 928 | def toggle_display_guests(self, to_pid): |
@@ -899,10 +951,10 @@ class Stats(object): | |||
899 | self.get(to_pid) | 951 | self.get(to_pid) |
900 | return 0 | 952 | return 0 |
901 | 953 | ||
954 | |||
902 | DELAY_DEFAULT = 3.0 | 955 | DELAY_DEFAULT = 3.0 |
903 | MAX_GUEST_NAME_LEN = 48 | 956 | MAX_GUEST_NAME_LEN = 48 |
904 | MAX_REGEX_LEN = 44 | 957 | MAX_REGEX_LEN = 44 |
905 | DEFAULT_REGEX = r'^[^\(]*$' | ||
906 | SORT_DEFAULT = 0 | 958 | SORT_DEFAULT = 0 |
907 | 959 | ||
908 | 960 | ||
@@ -969,7 +1021,7 @@ class Tui(object): | |||
969 | 1021 | ||
970 | return res | 1022 | return res |
971 | 1023 | ||
972 | def print_all_gnames(self, row): | 1024 | def _print_all_gnames(self, row): |
973 | """Print a list of all running guests along with their pids.""" | 1025 | """Print a list of all running guests along with their pids.""" |
974 | self.screen.addstr(row, 2, '%8s %-60s' % | 1026 | self.screen.addstr(row, 2, '%8s %-60s' % |
975 | ('Pid', 'Guest Name (fuzzy list, might be ' | 1027 | ('Pid', 'Guest Name (fuzzy list, might be ' |
@@ -1032,19 +1084,13 @@ class Tui(object): | |||
1032 | 1084 | ||
1033 | return name | 1085 | return name |
1034 | 1086 | ||
1035 | def update_drilldown(self): | 1087 | def _update_pid(self, pid): |
1036 | """Sets or removes a filter that only allows fields without braces.""" | ||
1037 | if not self.stats.fields_filter: | ||
1038 | self.stats.fields_filter = DEFAULT_REGEX | ||
1039 | |||
1040 | elif self.stats.fields_filter == DEFAULT_REGEX: | ||
1041 | self.stats.fields_filter = None | ||
1042 | |||
1043 | def update_pid(self, pid): | ||
1044 | """Propagates pid selection to stats object.""" | 1088 | """Propagates pid selection to stats object.""" |
1089 | self.screen.addstr(4, 1, 'Updating pid filter...') | ||
1090 | self.screen.refresh() | ||
1045 | self.stats.pid_filter = pid | 1091 | self.stats.pid_filter = pid |
1046 | 1092 | ||
1047 | def refresh_header(self, pid=None): | 1093 | def _refresh_header(self, pid=None): |
1048 | """Refreshes the header.""" | 1094 | """Refreshes the header.""" |
1049 | if pid is None: | 1095 | if pid is None: |
1050 | pid = self.stats.pid_filter | 1096 | pid = self.stats.pid_filter |
@@ -1059,8 +1105,7 @@ class Tui(object): | |||
1059 | .format(pid, gname), curses.A_BOLD) | 1105 | .format(pid, gname), curses.A_BOLD) |
1060 | else: | 1106 | else: |
1061 | self.screen.addstr(0, 0, 'kvm statistics - summary', curses.A_BOLD) | 1107 | self.screen.addstr(0, 0, 'kvm statistics - summary', curses.A_BOLD) |
1062 | if self.stats.fields_filter and self.stats.fields_filter \ | 1108 | if self.stats.fields_filter: |
1063 | != DEFAULT_REGEX: | ||
1064 | regex = self.stats.fields_filter | 1109 | regex = self.stats.fields_filter |
1065 | if len(regex) > MAX_REGEX_LEN: | 1110 | if len(regex) > MAX_REGEX_LEN: |
1066 | regex = regex[:MAX_REGEX_LEN] + '...' | 1111 | regex = regex[:MAX_REGEX_LEN] + '...' |
@@ -1075,56 +1120,99 @@ class Tui(object): | |||
1075 | self.screen.addstr(4, 1, 'Collecting data...') | 1120 | self.screen.addstr(4, 1, 'Collecting data...') |
1076 | self.screen.refresh() | 1121 | self.screen.refresh() |
1077 | 1122 | ||
1078 | def refresh_body(self, sleeptime): | 1123 | def _refresh_body(self, sleeptime): |
1124 | def is_child_field(field): | ||
1125 | return field.find('(') != -1 | ||
1126 | |||
1127 | def insert_child(sorted_items, child, values, parent): | ||
1128 | num = len(sorted_items) | ||
1129 | for i in range(0, num): | ||
1130 | # only add child if parent is present | ||
1131 | if parent.startswith(sorted_items[i][0]): | ||
1132 | sorted_items.insert(i + 1, (' ' + child, values)) | ||
1133 | |||
1134 | def get_sorted_events(self, stats): | ||
1135 | """ separate parent and child events """ | ||
1136 | if self._sorting == SORT_DEFAULT: | ||
1137 | def sortkey((_k, v)): | ||
1138 | # sort by (delta value, overall value) | ||
1139 | return (v.delta, v.value) | ||
1140 | else: | ||
1141 | def sortkey((_k, v)): | ||
1142 | # sort by overall value | ||
1143 | return v.value | ||
1144 | |||
1145 | childs = [] | ||
1146 | sorted_items = [] | ||
1147 | # we can't rule out child events to appear prior to parents even | ||
1148 | # when sorted - separate out all children first, and add in later | ||
1149 | for key, values in sorted(stats.items(), key=sortkey, | ||
1150 | reverse=True): | ||
1151 | if values == (0, 0): | ||
1152 | continue | ||
1153 | if key.find(' ') != -1: | ||
1154 | if not self.stats.child_events: | ||
1155 | continue | ||
1156 | childs.insert(0, (key, values)) | ||
1157 | else: | ||
1158 | sorted_items.append((key, values)) | ||
1159 | if self.stats.child_events: | ||
1160 | for key, values in childs: | ||
1161 | (child, parent) = key.split(' ') | ||
1162 | insert_child(sorted_items, child, values, parent) | ||
1163 | |||
1164 | return sorted_items | ||
1165 | |||
1079 | row = 3 | 1166 | row = 3 |
1080 | self.screen.move(row, 0) | 1167 | self.screen.move(row, 0) |
1081 | self.screen.clrtobot() | 1168 | self.screen.clrtobot() |
1082 | stats = self.stats.get(self._display_guests) | 1169 | stats = self.stats.get(self._display_guests) |
1083 | 1170 | total = 0. | |
1084 | def sortCurAvg(x): | 1171 | ctotal = 0. |
1085 | # sort by current events if available | 1172 | for key, values in stats.items(): |
1086 | if stats[x][1]: | 1173 | if self._display_guests: |
1087 | return (-stats[x][1], -stats[x][0]) | 1174 | if self.get_gname_from_pid(key): |
1175 | total += values.value | ||
1176 | continue | ||
1177 | if not key.find(' ') != -1: | ||
1178 | total += values.value | ||
1088 | else: | 1179 | else: |
1089 | return (0, -stats[x][0]) | 1180 | ctotal += values.value |
1181 | if total == 0.: | ||
1182 | # we don't have any fields, or all non-child events are filtered | ||
1183 | total = ctotal | ||
1090 | 1184 | ||
1091 | def sortTotal(x): | 1185 | # print events |
1092 | # sort by totals | ||
1093 | return (0, -stats[x][0]) | ||
1094 | total = 0. | ||
1095 | for key in stats.keys(): | ||
1096 | if key.find('(') is -1: | ||
1097 | total += stats[key][0] | ||
1098 | if self._sorting == SORT_DEFAULT: | ||
1099 | sortkey = sortCurAvg | ||
1100 | else: | ||
1101 | sortkey = sortTotal | ||
1102 | tavg = 0 | 1186 | tavg = 0 |
1103 | for key in sorted(stats.keys(), key=sortkey): | 1187 | tcur = 0 |
1104 | if row >= self.screen.getmaxyx()[0] - 1: | 1188 | for key, values in get_sorted_events(self, stats): |
1105 | break | 1189 | if row >= self.screen.getmaxyx()[0] - 1 or values == (0, 0): |
1106 | values = stats[key] | ||
1107 | if not values[0] and not values[1]: | ||
1108 | break | 1190 | break |
1109 | if values[0] is not None: | 1191 | if self._display_guests: |
1110 | cur = int(round(values[1] / sleeptime)) if values[1] else '' | 1192 | key = self.get_gname_from_pid(key) |
1111 | if self._display_guests: | 1193 | if not key: |
1112 | key = self.get_gname_from_pid(key) | 1194 | continue |
1113 | self.screen.addstr(row, 1, '%-40s %10d%7.1f %8s' % | 1195 | cur = int(round(values.delta / sleeptime)) if values.delta else '' |
1114 | (key, values[0], values[0] * 100 / total, | 1196 | if key[0] != ' ': |
1115 | cur)) | 1197 | if values.delta: |
1116 | if cur is not '' and key.find('(') is -1: | 1198 | tcur += values.delta |
1117 | tavg += cur | 1199 | ptotal = values.value |
1200 | ltotal = total | ||
1201 | else: | ||
1202 | ltotal = ptotal | ||
1203 | self.screen.addstr(row, 1, '%-40s %10d%7.1f %8s' % (key, | ||
1204 | values.value, | ||
1205 | values.value * 100 / float(ltotal), cur)) | ||
1118 | row += 1 | 1206 | row += 1 |
1119 | if row == 3: | 1207 | if row == 3: |
1120 | self.screen.addstr(4, 1, 'No matching events reported yet') | 1208 | self.screen.addstr(4, 1, 'No matching events reported yet') |
1121 | else: | 1209 | if row > 4: |
1210 | tavg = int(round(tcur / sleeptime)) if tcur > 0 else '' | ||
1122 | self.screen.addstr(row, 1, '%-40s %10d %8s' % | 1211 | self.screen.addstr(row, 1, '%-40s %10d %8s' % |
1123 | ('Total', total, tavg if tavg else ''), | 1212 | ('Total', total, tavg), curses.A_BOLD) |
1124 | curses.A_BOLD) | ||
1125 | self.screen.refresh() | 1213 | self.screen.refresh() |
1126 | 1214 | ||
1127 | def show_msg(self, text): | 1215 | def _show_msg(self, text): |
1128 | """Display message centered text and exit on key press""" | 1216 | """Display message centered text and exit on key press""" |
1129 | hint = 'Press any key to continue' | 1217 | hint = 'Press any key to continue' |
1130 | curses.cbreak() | 1218 | curses.cbreak() |
@@ -1139,16 +1227,16 @@ class Tui(object): | |||
1139 | curses.A_STANDOUT) | 1227 | curses.A_STANDOUT) |
1140 | self.screen.getkey() | 1228 | self.screen.getkey() |
1141 | 1229 | ||
1142 | def show_help_interactive(self): | 1230 | def _show_help_interactive(self): |
1143 | """Display help with list of interactive commands""" | 1231 | """Display help with list of interactive commands""" |
1144 | msg = (' b toggle events by guests (debugfs only, honors' | 1232 | msg = (' b toggle events by guests (debugfs only, honors' |
1145 | ' filters)', | 1233 | ' filters)', |
1146 | ' c clear filter', | 1234 | ' c clear filter', |
1147 | ' f filter by regular expression', | 1235 | ' f filter by regular expression', |
1148 | ' g filter by guest name', | 1236 | ' g filter by guest name/PID', |
1149 | ' h display interactive commands reference', | 1237 | ' h display interactive commands reference', |
1150 | ' o toggle sorting order (Total vs CurAvg/s)', | 1238 | ' o toggle sorting order (Total vs CurAvg/s)', |
1151 | ' p filter by PID', | 1239 | ' p filter by guest name/PID', |
1152 | ' q quit', | 1240 | ' q quit', |
1153 | ' r reset stats', | 1241 | ' r reset stats', |
1154 | ' s set update interval', | 1242 | ' s set update interval', |
@@ -1165,14 +1253,15 @@ class Tui(object): | |||
1165 | self.screen.addstr(row, 0, line) | 1253 | self.screen.addstr(row, 0, line) |
1166 | row += 1 | 1254 | row += 1 |
1167 | self.screen.getkey() | 1255 | self.screen.getkey() |
1168 | self.refresh_header() | 1256 | self._refresh_header() |
1169 | 1257 | ||
1170 | def show_filter_selection(self): | 1258 | def _show_filter_selection(self): |
1171 | """Draws filter selection mask. | 1259 | """Draws filter selection mask. |
1172 | 1260 | ||
1173 | Asks for a valid regex and sets the fields filter accordingly. | 1261 | Asks for a valid regex and sets the fields filter accordingly. |
1174 | 1262 | ||
1175 | """ | 1263 | """ |
1264 | msg = '' | ||
1176 | while True: | 1265 | while True: |
1177 | self.screen.erase() | 1266 | self.screen.erase() |
1178 | self.screen.addstr(0, 0, | 1267 | self.screen.addstr(0, 0, |
@@ -1181,61 +1270,25 @@ class Tui(object): | |||
1181 | self.screen.addstr(2, 0, | 1270 | self.screen.addstr(2, 0, |
1182 | "Current regex: {0}" | 1271 | "Current regex: {0}" |
1183 | .format(self.stats.fields_filter)) | 1272 | .format(self.stats.fields_filter)) |
1273 | self.screen.addstr(5, 0, msg) | ||
1184 | self.screen.addstr(3, 0, "New regex: ") | 1274 | self.screen.addstr(3, 0, "New regex: ") |
1185 | curses.echo() | 1275 | curses.echo() |
1186 | regex = self.screen.getstr().decode(ENCODING) | 1276 | regex = self.screen.getstr().decode(ENCODING) |
1187 | curses.noecho() | 1277 | curses.noecho() |
1188 | if len(regex) == 0: | 1278 | if len(regex) == 0: |
1189 | self.stats.fields_filter = DEFAULT_REGEX | 1279 | self.stats.fields_filter = '' |
1190 | self.refresh_header() | 1280 | self._refresh_header() |
1191 | return | 1281 | return |
1192 | try: | 1282 | try: |
1193 | re.compile(regex) | 1283 | re.compile(regex) |
1194 | self.stats.fields_filter = regex | 1284 | self.stats.fields_filter = regex |
1195 | self.refresh_header() | 1285 | self._refresh_header() |
1196 | return | 1286 | return |
1197 | except re.error: | 1287 | except re.error: |
1288 | msg = '"' + regex + '": Not a valid regular expression' | ||
1198 | continue | 1289 | continue |
1199 | 1290 | ||
1200 | def show_vm_selection_by_pid(self): | 1291 | def _show_set_update_interval(self): |
1201 | """Draws PID selection mask. | ||
1202 | |||
1203 | Asks for a pid until a valid pid or 0 has been entered. | ||
1204 | |||
1205 | """ | ||
1206 | msg = '' | ||
1207 | while True: | ||
1208 | self.screen.erase() | ||
1209 | self.screen.addstr(0, 0, | ||
1210 | 'Show statistics for specific pid.', | ||
1211 | curses.A_BOLD) | ||
1212 | self.screen.addstr(1, 0, | ||
1213 | 'This might limit the shown data to the trace ' | ||
1214 | 'statistics.') | ||
1215 | self.screen.addstr(5, 0, msg) | ||
1216 | self.print_all_gnames(7) | ||
1217 | |||
1218 | curses.echo() | ||
1219 | self.screen.addstr(3, 0, "Pid [0 or pid]: ") | ||
1220 | pid = self.screen.getstr().decode(ENCODING) | ||
1221 | curses.noecho() | ||
1222 | |||
1223 | try: | ||
1224 | if len(pid) > 0: | ||
1225 | pid = int(pid) | ||
1226 | if pid != 0 and not os.path.isdir(os.path.join('/proc/', | ||
1227 | str(pid))): | ||
1228 | msg = '"' + str(pid) + '": Not a running process' | ||
1229 | continue | ||
1230 | else: | ||
1231 | pid = 0 | ||
1232 | self.refresh_header(pid) | ||
1233 | self.update_pid(pid) | ||
1234 | break | ||
1235 | except ValueError: | ||
1236 | msg = '"' + str(pid) + '": Not a valid pid' | ||
1237 | |||
1238 | def show_set_update_interval(self): | ||
1239 | """Draws update interval selection mask.""" | 1292 | """Draws update interval selection mask.""" |
1240 | msg = '' | 1293 | msg = '' |
1241 | while True: | 1294 | while True: |
@@ -1265,60 +1318,67 @@ class Tui(object): | |||
1265 | 1318 | ||
1266 | except ValueError: | 1319 | except ValueError: |
1267 | msg = '"' + str(val) + '": Invalid value' | 1320 | msg = '"' + str(val) + '": Invalid value' |
1268 | self.refresh_header() | 1321 | self._refresh_header() |
1269 | 1322 | ||
1270 | def show_vm_selection_by_guest_name(self): | 1323 | def _show_vm_selection_by_guest(self): |
1271 | """Draws guest selection mask. | 1324 | """Draws guest selection mask. |
1272 | 1325 | ||
1273 | Asks for a guest name until a valid guest name or '' is entered. | 1326 | Asks for a guest name or pid until a valid guest name or '' is entered. |
1274 | 1327 | ||
1275 | """ | 1328 | """ |
1276 | msg = '' | 1329 | msg = '' |
1277 | while True: | 1330 | while True: |
1278 | self.screen.erase() | 1331 | self.screen.erase() |
1279 | self.screen.addstr(0, 0, | 1332 | self.screen.addstr(0, 0, |
1280 | 'Show statistics for specific guest.', | 1333 | 'Show statistics for specific guest or pid.', |
1281 | curses.A_BOLD) | 1334 | curses.A_BOLD) |
1282 | self.screen.addstr(1, 0, | 1335 | self.screen.addstr(1, 0, |
1283 | 'This might limit the shown data to the trace ' | 1336 | 'This might limit the shown data to the trace ' |
1284 | 'statistics.') | 1337 | 'statistics.') |
1285 | self.screen.addstr(5, 0, msg) | 1338 | self.screen.addstr(5, 0, msg) |
1286 | self.print_all_gnames(7) | 1339 | self._print_all_gnames(7) |
1287 | curses.echo() | 1340 | curses.echo() |
1288 | self.screen.addstr(3, 0, "Guest [ENTER or guest]: ") | 1341 | curses.curs_set(1) |
1289 | gname = self.screen.getstr().decode(ENCODING) | 1342 | self.screen.addstr(3, 0, "Guest or pid [ENTER exits]: ") |
1343 | guest = self.screen.getstr().decode(ENCODING) | ||
1290 | curses.noecho() | 1344 | curses.noecho() |
1291 | 1345 | ||
1292 | if not gname: | 1346 | pid = 0 |
1293 | self.refresh_header(0) | 1347 | if not guest or guest == '0': |
1294 | self.update_pid(0) | ||
1295 | break | 1348 | break |
1296 | else: | 1349 | if guest.isdigit(): |
1297 | pids = [] | 1350 | if not os.path.isdir(os.path.join('/proc/', guest)): |
1298 | try: | 1351 | msg = '"' + guest + '": Not a running process' |
1299 | pids = self.get_pid_from_gname(gname) | ||
1300 | except: | ||
1301 | msg = '"' + gname + '": Internal error while searching, ' \ | ||
1302 | 'use pid filter instead' | ||
1303 | continue | ||
1304 | if len(pids) == 0: | ||
1305 | msg = '"' + gname + '": Not an active guest' | ||
1306 | continue | 1352 | continue |
1307 | if len(pids) > 1: | 1353 | pid = int(guest) |
1308 | msg = '"' + gname + '": Multiple matches found, use pid ' \ | ||
1309 | 'filter instead' | ||
1310 | continue | ||
1311 | self.refresh_header(pids[0]) | ||
1312 | self.update_pid(pids[0]) | ||
1313 | break | 1354 | break |
1355 | pids = [] | ||
1356 | try: | ||
1357 | pids = self.get_pid_from_gname(guest) | ||
1358 | except: | ||
1359 | msg = '"' + guest + '": Internal error while searching, ' \ | ||
1360 | 'use pid filter instead' | ||
1361 | continue | ||
1362 | if len(pids) == 0: | ||
1363 | msg = '"' + guest + '": Not an active guest' | ||
1364 | continue | ||
1365 | if len(pids) > 1: | ||
1366 | msg = '"' + guest + '": Multiple matches found, use pid ' \ | ||
1367 | 'filter instead' | ||
1368 | continue | ||
1369 | pid = pids[0] | ||
1370 | break | ||
1371 | curses.curs_set(0) | ||
1372 | self._refresh_header(pid) | ||
1373 | self._update_pid(pid) | ||
1314 | 1374 | ||
1315 | def show_stats(self): | 1375 | def show_stats(self): |
1316 | """Refreshes the screen and processes user input.""" | 1376 | """Refreshes the screen and processes user input.""" |
1317 | sleeptime = self._delay_initial | 1377 | sleeptime = self._delay_initial |
1318 | self.refresh_header() | 1378 | self._refresh_header() |
1319 | start = 0.0 # result based on init value never appears on screen | 1379 | start = 0.0 # result based on init value never appears on screen |
1320 | while True: | 1380 | while True: |
1321 | self.refresh_body(time.time() - start) | 1381 | self._refresh_body(time.time() - start) |
1322 | curses.halfdelay(int(sleeptime * 10)) | 1382 | curses.halfdelay(int(sleeptime * 10)) |
1323 | start = time.time() | 1383 | start = time.time() |
1324 | sleeptime = self._delay_regular | 1384 | sleeptime = self._delay_regular |
@@ -1327,47 +1387,39 @@ class Tui(object): | |||
1327 | if char == 'b': | 1387 | if char == 'b': |
1328 | self._display_guests = not self._display_guests | 1388 | self._display_guests = not self._display_guests |
1329 | if self.stats.toggle_display_guests(self._display_guests): | 1389 | if self.stats.toggle_display_guests(self._display_guests): |
1330 | self.show_msg(['Command not available with tracepoints' | 1390 | self._show_msg(['Command not available with ' |
1331 | ' enabled', 'Restart with debugfs only ' | 1391 | 'tracepoints enabled', 'Restart with ' |
1332 | '(see option \'-d\') and try again!']) | 1392 | 'debugfs only (see option \'-d\') and ' |
1393 | 'try again!']) | ||
1333 | self._display_guests = not self._display_guests | 1394 | self._display_guests = not self._display_guests |
1334 | self.refresh_header() | 1395 | self._refresh_header() |
1335 | if char == 'c': | 1396 | if char == 'c': |
1336 | self.stats.fields_filter = DEFAULT_REGEX | 1397 | self.stats.fields_filter = '' |
1337 | self.refresh_header(0) | 1398 | self._refresh_header(0) |
1338 | self.update_pid(0) | 1399 | self._update_pid(0) |
1339 | if char == 'f': | 1400 | if char == 'f': |
1340 | curses.curs_set(1) | 1401 | curses.curs_set(1) |
1341 | self.show_filter_selection() | 1402 | self._show_filter_selection() |
1342 | curses.curs_set(0) | 1403 | curses.curs_set(0) |
1343 | sleeptime = self._delay_initial | 1404 | sleeptime = self._delay_initial |
1344 | if char == 'g': | 1405 | if char == 'g' or char == 'p': |
1345 | curses.curs_set(1) | 1406 | self._show_vm_selection_by_guest() |
1346 | self.show_vm_selection_by_guest_name() | ||
1347 | curses.curs_set(0) | ||
1348 | sleeptime = self._delay_initial | 1407 | sleeptime = self._delay_initial |
1349 | if char == 'h': | 1408 | if char == 'h': |
1350 | self.show_help_interactive() | 1409 | self._show_help_interactive() |
1351 | if char == 'o': | 1410 | if char == 'o': |
1352 | self._sorting = not self._sorting | 1411 | self._sorting = not self._sorting |
1353 | if char == 'p': | ||
1354 | curses.curs_set(1) | ||
1355 | self.show_vm_selection_by_pid() | ||
1356 | curses.curs_set(0) | ||
1357 | sleeptime = self._delay_initial | ||
1358 | if char == 'q': | 1412 | if char == 'q': |
1359 | break | 1413 | break |
1360 | if char == 'r': | 1414 | if char == 'r': |
1361 | self.stats.reset() | 1415 | self.stats.reset() |
1362 | if char == 's': | 1416 | if char == 's': |
1363 | curses.curs_set(1) | 1417 | curses.curs_set(1) |
1364 | self.show_set_update_interval() | 1418 | self._show_set_update_interval() |
1365 | curses.curs_set(0) | 1419 | curses.curs_set(0) |
1366 | sleeptime = self._delay_initial | 1420 | sleeptime = self._delay_initial |
1367 | if char == 'x': | 1421 | if char == 'x': |
1368 | self.update_drilldown() | 1422 | self.stats.child_events = not self.stats.child_events |
1369 | # prevents display of current values on next refresh | ||
1370 | self.stats.get(self._display_guests) | ||
1371 | except KeyboardInterrupt: | 1423 | except KeyboardInterrupt: |
1372 | break | 1424 | break |
1373 | except curses.error: | 1425 | except curses.error: |
@@ -1380,9 +1432,9 @@ def batch(stats): | |||
1380 | s = stats.get() | 1432 | s = stats.get() |
1381 | time.sleep(1) | 1433 | time.sleep(1) |
1382 | s = stats.get() | 1434 | s = stats.get() |
1383 | for key in sorted(s.keys()): | 1435 | for key, values in sorted(s.items()): |
1384 | values = s[key] | 1436 | print('%-42s%10d%10d' % (key.split(' ')[0], values.value, |
1385 | print('%-42s%10d%10d' % (key, values[0], values[1])) | 1437 | values.delta)) |
1386 | except KeyboardInterrupt: | 1438 | except KeyboardInterrupt: |
1387 | pass | 1439 | pass |
1388 | 1440 | ||
@@ -1392,14 +1444,14 @@ def log(stats): | |||
1392 | keys = sorted(stats.get().keys()) | 1444 | keys = sorted(stats.get().keys()) |
1393 | 1445 | ||
1394 | def banner(): | 1446 | def banner(): |
1395 | for k in keys: | 1447 | for key in keys: |
1396 | print(k, end=' ') | 1448 | print(key.split(' ')[0], end=' ') |
1397 | print() | 1449 | print() |
1398 | 1450 | ||
1399 | def statline(): | 1451 | def statline(): |
1400 | s = stats.get() | 1452 | s = stats.get() |
1401 | for k in keys: | 1453 | for key in keys: |
1402 | print(' %9d' % s[k][1], end=' ') | 1454 | print(' %9d' % s[key].delta, end=' ') |
1403 | print() | 1455 | print() |
1404 | line = 0 | 1456 | line = 0 |
1405 | banner_repeat = 20 | 1457 | banner_repeat = 20 |
@@ -1504,7 +1556,7 @@ Press any other key to refresh statistics immediately. | |||
1504 | ) | 1556 | ) |
1505 | optparser.add_option('-f', '--fields', | 1557 | optparser.add_option('-f', '--fields', |
1506 | action='store', | 1558 | action='store', |
1507 | default=DEFAULT_REGEX, | 1559 | default='', |
1508 | dest='fields', | 1560 | dest='fields', |
1509 | help='''fields to display (regex) | 1561 | help='''fields to display (regex) |
1510 | "-f help" for a list of available events''', | 1562 | "-f help" for a list of available events''', |
@@ -1539,17 +1591,6 @@ Press any other key to refresh statistics immediately. | |||
1539 | 1591 | ||
1540 | def check_access(options): | 1592 | def check_access(options): |
1541 | """Exits if the current user can't access all needed directories.""" | 1593 | """Exits if the current user can't access all needed directories.""" |
1542 | if not os.path.exists('/sys/kernel/debug'): | ||
1543 | sys.stderr.write('Please enable CONFIG_DEBUG_FS in your kernel.') | ||
1544 | sys.exit(1) | ||
1545 | |||
1546 | if not os.path.exists(PATH_DEBUGFS_KVM): | ||
1547 | sys.stderr.write("Please make sure, that debugfs is mounted and " | ||
1548 | "readable by the current user:\n" | ||
1549 | "('mount -t debugfs debugfs /sys/kernel/debug')\n" | ||
1550 | "Also ensure, that the kvm modules are loaded.\n") | ||
1551 | sys.exit(1) | ||
1552 | |||
1553 | if not os.path.exists(PATH_DEBUGFS_TRACING) and (options.tracepoints or | 1594 | if not os.path.exists(PATH_DEBUGFS_TRACING) and (options.tracepoints or |
1554 | not options.debugfs): | 1595 | not options.debugfs): |
1555 | sys.stderr.write("Please enable CONFIG_TRACING in your kernel " | 1596 | sys.stderr.write("Please enable CONFIG_TRACING in your kernel " |
@@ -1567,7 +1608,33 @@ def check_access(options): | |||
1567 | return options | 1608 | return options |
1568 | 1609 | ||
1569 | 1610 | ||
1611 | def assign_globals(): | ||
1612 | global PATH_DEBUGFS_KVM | ||
1613 | global PATH_DEBUGFS_TRACING | ||
1614 | |||
1615 | debugfs = '' | ||
1616 | for line in file('/proc/mounts'): | ||
1617 | if line.split(' ')[0] == 'debugfs': | ||
1618 | debugfs = line.split(' ')[1] | ||
1619 | break | ||
1620 | if debugfs == '': | ||
1621 | sys.stderr.write("Please make sure that CONFIG_DEBUG_FS is enabled in " | ||
1622 | "your kernel, mounted and\nreadable by the current " | ||
1623 | "user:\n" | ||
1624 | "('mount -t debugfs debugfs /sys/kernel/debug')\n") | ||
1625 | sys.exit(1) | ||
1626 | |||
1627 | PATH_DEBUGFS_KVM = os.path.join(debugfs, 'kvm') | ||
1628 | PATH_DEBUGFS_TRACING = os.path.join(debugfs, 'tracing') | ||
1629 | |||
1630 | if not os.path.exists(PATH_DEBUGFS_KVM): | ||
1631 | sys.stderr.write("Please make sure that CONFIG_KVM is enabled in " | ||
1632 | "your kernel and that the modules are loaded.\n") | ||
1633 | sys.exit(1) | ||
1634 | |||
1635 | |||
1570 | def main(): | 1636 | def main(): |
1637 | assign_globals() | ||
1571 | options = get_options() | 1638 | options = get_options() |
1572 | options = check_access(options) | 1639 | options = check_access(options) |
1573 | 1640 | ||
diff --git a/tools/kvm/kvm_stat/kvm_stat.txt b/tools/kvm/kvm_stat/kvm_stat.txt index b5b3810c9e94..0811d860fe75 100644 --- a/tools/kvm/kvm_stat/kvm_stat.txt +++ b/tools/kvm/kvm_stat/kvm_stat.txt | |||
@@ -35,13 +35,13 @@ INTERACTIVE COMMANDS | |||
35 | 35 | ||
36 | *f*:: filter by regular expression | 36 | *f*:: filter by regular expression |
37 | 37 | ||
38 | *g*:: filter by guest name | 38 | *g*:: filter by guest name/PID |
39 | 39 | ||
40 | *h*:: display interactive commands reference | 40 | *h*:: display interactive commands reference |
41 | 41 | ||
42 | *o*:: toggle sorting order (Total vs CurAvg/s) | 42 | *o*:: toggle sorting order (Total vs CurAvg/s) |
43 | 43 | ||
44 | *p*:: filter by PID | 44 | *p*:: filter by guest name/PID |
45 | 45 | ||
46 | *q*:: quit | 46 | *q*:: quit |
47 | 47 | ||