diff options
author | Janosch Frank <frankja@linux.vnet.ibm.com> | 2016-05-18 07:26:24 -0400 |
---|---|---|
committer | Paolo Bonzini <pbonzini@redhat.com> | 2016-05-25 10:12:06 -0400 |
commit | f0cf040f842242d55744c2606e8b7177507fbbb0 (patch) | |
tree | f277b06f090e47953d50e88db8ec1d13d1a585bf | |
parent | 536a6f88c49dd739961ffd53774775afed852c83 (diff) |
tools: kvm_stat: Introduce pid monitoring
Having stats for single VMs can help to determine the problem of a VM
without the need of running other tools like perf.
The tracepoints already allowed pid level monitoring, but kvm_stat
didn't have support for it till now. Support for the newly implemented
debugfs vm monitoring was also implemented.
Signed-off-by: Janosch Frank <frankja@linux.vnet.ibm.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
-rwxr-xr-x | tools/kvm/kvm_stat/kvm_stat | 183 | ||||
-rw-r--r-- | tools/kvm/kvm_stat/kvm_stat.txt | 6 |
2 files changed, 167 insertions, 22 deletions
diff --git a/tools/kvm/kvm_stat/kvm_stat b/tools/kvm/kvm_stat/kvm_stat index 27d217a4c4c1..b4d50e8eb75a 100755 --- a/tools/kvm/kvm_stat/kvm_stat +++ b/tools/kvm/kvm_stat/kvm_stat | |||
@@ -367,12 +367,16 @@ class Group(object): | |||
367 | os.read(self.events[0].fd, length)))) | 367 | os.read(self.events[0].fd, length)))) |
368 | 368 | ||
369 | class Event(object): | 369 | class Event(object): |
370 | def __init__(self, name, group, trace_cpu, trace_point, trace_filter, | 370 | def __init__(self, name, group, trace_cpu, trace_pid, trace_point, |
371 | trace_set='kvm'): | 371 | trace_filter, trace_set='kvm'): |
372 | self.name = name | 372 | self.name = name |
373 | self.fd = None | 373 | self.fd = None |
374 | self.setup_event(group, trace_cpu, trace_point, trace_filter, | 374 | self.setup_event(group, trace_cpu, trace_pid, trace_point, |
375 | trace_set) | 375 | trace_filter, trace_set) |
376 | |||
377 | def __del__(self): | ||
378 | if self.fd: | ||
379 | os.close(self.fd) | ||
376 | 380 | ||
377 | def setup_event_attribute(self, trace_set, trace_point): | 381 | def setup_event_attribute(self, trace_set, trace_point): |
378 | id_path = os.path.join(PATH_DEBUGFS_TRACING, 'events', trace_set, | 382 | id_path = os.path.join(PATH_DEBUGFS_TRACING, 'events', trace_set, |
@@ -382,16 +386,16 @@ class Event(object): | |||
382 | event_attr.config = int(open(id_path).read()) | 386 | event_attr.config = int(open(id_path).read()) |
383 | return event_attr | 387 | return event_attr |
384 | 388 | ||
385 | def setup_event(self, group, trace_cpu, trace_point, trace_filter, | 389 | def setup_event(self, group, trace_cpu, trace_pid, trace_point, |
386 | trace_set): | 390 | trace_filter, trace_set): |
387 | event_attr = self.setup_event_attribute(trace_set, trace_point) | 391 | event_attr = self.setup_event_attribute(trace_set, trace_point) |
388 | 392 | ||
389 | group_leader = -1 | 393 | group_leader = -1 |
390 | if group.events: | 394 | if group.events: |
391 | group_leader = group.events[0].fd | 395 | group_leader = group.events[0].fd |
392 | 396 | ||
393 | fd = perf_event_open(event_attr, -1, trace_cpu, | 397 | fd = perf_event_open(event_attr, trace_pid, |
394 | group_leader, 0) | 398 | trace_cpu, group_leader, 0) |
395 | if fd == -1: | 399 | if fd == -1: |
396 | err = ctypes.get_errno() | 400 | err = ctypes.get_errno() |
397 | raise OSError(err, os.strerror(err), | 401 | raise OSError(err, os.strerror(err), |
@@ -417,8 +421,7 @@ class TracepointProvider(object): | |||
417 | self.group_leaders = [] | 421 | self.group_leaders = [] |
418 | self.filters = get_filters() | 422 | self.filters = get_filters() |
419 | self._fields = self.get_available_fields() | 423 | self._fields = self.get_available_fields() |
420 | self.setup_traces() | 424 | self._pid = 0 |
421 | self.fields = self._fields | ||
422 | 425 | ||
423 | def get_available_fields(self): | 426 | def get_available_fields(self): |
424 | path = os.path.join(PATH_DEBUGFS_TRACING, 'events', 'kvm') | 427 | path = os.path.join(PATH_DEBUGFS_TRACING, 'events', 'kvm') |
@@ -433,11 +436,17 @@ class TracepointProvider(object): | |||
433 | return fields | 436 | return fields |
434 | 437 | ||
435 | def setup_traces(self): | 438 | def setup_traces(self): |
436 | cpus = get_online_cpus() | 439 | if self._pid > 0: |
440 | # Fetch list of all threads of the monitored pid, as qemu | ||
441 | # starts a thread for each vcpu. | ||
442 | path = os.path.join('/proc', str(self._pid), 'task') | ||
443 | groupids = walkdir(path)[1] | ||
444 | else: | ||
445 | groupids = get_online_cpus() | ||
437 | 446 | ||
438 | # The constant is needed as a buffer for python libs, std | 447 | # The constant is needed as a buffer for python libs, std |
439 | # streams and other files that the script opens. | 448 | # streams and other files that the script opens. |
440 | newlim = len(cpus) * len(self._fields) + 50 | 449 | newlim = len(groupids) * len(self._fields) + 50 |
441 | try: | 450 | try: |
442 | softlim_, hardlim = resource.getrlimit(resource.RLIMIT_NOFILE) | 451 | softlim_, hardlim = resource.getrlimit(resource.RLIMIT_NOFILE) |
443 | 452 | ||
@@ -451,7 +460,7 @@ class TracepointProvider(object): | |||
451 | except ValueError: | 460 | except ValueError: |
452 | sys.exit("NOFILE rlimit could not be raised to {0}".format(newlim)) | 461 | sys.exit("NOFILE rlimit could not be raised to {0}".format(newlim)) |
453 | 462 | ||
454 | for cpu in cpus: | 463 | for groupid in groupids: |
455 | group = Group() | 464 | group = Group() |
456 | for name in self._fields: | 465 | for name in self._fields: |
457 | tracepoint = name | 466 | tracepoint = name |
@@ -463,11 +472,22 @@ class TracepointProvider(object): | |||
463 | (self.filters[tracepoint][0], | 472 | (self.filters[tracepoint][0], |
464 | self.filters[tracepoint][1][sub])) | 473 | self.filters[tracepoint][1][sub])) |
465 | 474 | ||
475 | # From perf_event_open(2): | ||
476 | # pid > 0 and cpu == -1 | ||
477 | # This measures the specified process/thread on any CPU. | ||
478 | # | ||
479 | # pid == -1 and cpu >= 0 | ||
480 | # This measures all processes/threads on the specified CPU. | ||
481 | trace_cpu = groupid if self._pid == 0 else -1 | ||
482 | trace_pid = int(groupid) if self._pid != 0 else -1 | ||
483 | |||
466 | group.add_event(Event(name=name, | 484 | group.add_event(Event(name=name, |
467 | group=group, | 485 | group=group, |
468 | trace_cpu=cpu, | 486 | trace_cpu=trace_cpu, |
487 | trace_pid=trace_pid, | ||
469 | trace_point=tracepoint, | 488 | trace_point=tracepoint, |
470 | trace_filter=tracefilter)) | 489 | trace_filter=tracefilter)) |
490 | |||
471 | self.group_leaders.append(group) | 491 | self.group_leaders.append(group) |
472 | 492 | ||
473 | def available_fields(self): | 493 | def available_fields(self): |
@@ -491,6 +511,17 @@ class TracepointProvider(object): | |||
491 | if index != 0: | 511 | if index != 0: |
492 | event.disable() | 512 | event.disable() |
493 | 513 | ||
514 | @property | ||
515 | def pid(self): | ||
516 | return self._pid | ||
517 | |||
518 | @pid.setter | ||
519 | def pid(self, pid): | ||
520 | self._pid = pid | ||
521 | self.group_leaders = [] | ||
522 | self.setup_traces() | ||
523 | self.fields = self._fields | ||
524 | |||
494 | def read(self): | 525 | def read(self): |
495 | ret = defaultdict(int) | 526 | ret = defaultdict(int) |
496 | for group in self.group_leaders: | 527 | for group in self.group_leaders: |
@@ -502,6 +533,8 @@ class TracepointProvider(object): | |||
502 | class DebugfsProvider(object): | 533 | class DebugfsProvider(object): |
503 | def __init__(self): | 534 | def __init__(self): |
504 | self._fields = self.get_available_fields() | 535 | self._fields = self.get_available_fields() |
536 | self._pid = 0 | ||
537 | self.do_read = True | ||
505 | 538 | ||
506 | def get_available_fields(self): | 539 | def get_available_fields(self): |
507 | return walkdir(PATH_DEBUGFS_KVM)[2] | 540 | return walkdir(PATH_DEBUGFS_KVM)[2] |
@@ -514,16 +547,57 @@ class DebugfsProvider(object): | |||
514 | def fields(self, fields): | 547 | def fields(self, fields): |
515 | self._fields = fields | 548 | self._fields = fields |
516 | 549 | ||
550 | @property | ||
551 | def pid(self): | ||
552 | return self._pid | ||
553 | |||
554 | @pid.setter | ||
555 | def pid(self, pid): | ||
556 | if pid != 0: | ||
557 | self._pid = pid | ||
558 | |||
559 | vms = walkdir(PATH_DEBUGFS_KVM)[1] | ||
560 | if len(vms) == 0: | ||
561 | self.do_read = False | ||
562 | |||
563 | self.paths = filter(lambda x: "{}-".format(pid) in x, vms) | ||
564 | |||
565 | else: | ||
566 | self.paths = [''] | ||
567 | self.do_read = True | ||
568 | |||
517 | def read(self): | 569 | def read(self): |
518 | def val(key): | 570 | """Returns a dict with format:'file name / field -> current value'.""" |
519 | return int(file(PATH_DEBUGFS_KVM + '/' + key).read()) | 571 | results = {} |
520 | return dict([(key, val(key)) for key in self._fields]) | 572 | |
573 | # If no debugfs filtering support is available, then don't read. | ||
574 | if not self.do_read: | ||
575 | return results | ||
576 | |||
577 | for path in self.paths: | ||
578 | for field in self._fields: | ||
579 | results[field] = results.get(field, 0) \ | ||
580 | + self.read_field(field, path) | ||
581 | |||
582 | return results | ||
583 | |||
584 | def read_field(self, field, path): | ||
585 | """Returns the value of a single field from a specific VM.""" | ||
586 | try: | ||
587 | return int(open(os.path.join(PATH_DEBUGFS_KVM, | ||
588 | path, | ||
589 | field)) | ||
590 | .read()) | ||
591 | except IOError: | ||
592 | return 0 | ||
521 | 593 | ||
522 | class Stats(object): | 594 | class Stats(object): |
523 | def __init__(self, providers, fields=None): | 595 | def __init__(self, providers, pid, fields=None): |
524 | self.providers = providers | 596 | self.providers = providers |
597 | self._pid_filter = pid | ||
525 | self._fields_filter = fields | 598 | self._fields_filter = fields |
526 | self.values = {} | 599 | self.values = {} |
600 | self.update_provider_pid() | ||
527 | self.update_provider_filters() | 601 | self.update_provider_filters() |
528 | 602 | ||
529 | def update_provider_filters(self): | 603 | def update_provider_filters(self): |
@@ -540,6 +614,10 @@ class Stats(object): | |||
540 | if wanted(key)] | 614 | if wanted(key)] |
541 | provider.fields = provider_fields | 615 | provider.fields = provider_fields |
542 | 616 | ||
617 | def update_provider_pid(self): | ||
618 | for provider in self.providers: | ||
619 | provider.pid = self._pid_filter | ||
620 | |||
543 | @property | 621 | @property |
544 | def fields_filter(self): | 622 | def fields_filter(self): |
545 | return self._fields_filter | 623 | return self._fields_filter |
@@ -549,6 +627,16 @@ class Stats(object): | |||
549 | self._fields_filter = fields_filter | 627 | self._fields_filter = fields_filter |
550 | self.update_provider_filters() | 628 | self.update_provider_filters() |
551 | 629 | ||
630 | @property | ||
631 | def pid_filter(self): | ||
632 | return self._pid_filter | ||
633 | |||
634 | @pid_filter.setter | ||
635 | def pid_filter(self, pid): | ||
636 | self._pid_filter = pid | ||
637 | self.values = {} | ||
638 | self.update_provider_pid() | ||
639 | |||
552 | def get(self): | 640 | def get(self): |
553 | for provider in self.providers: | 641 | for provider in self.providers: |
554 | new = provider.read() | 642 | new = provider.read() |
@@ -605,9 +693,17 @@ class Tui(object): | |||
605 | elif self.stats.fields_filter == r'^[^\(]*$': | 693 | elif self.stats.fields_filter == r'^[^\(]*$': |
606 | self.stats.fields_filter = None | 694 | self.stats.fields_filter = None |
607 | 695 | ||
696 | def update_pid(self, pid): | ||
697 | self.stats.pid_filter = pid | ||
698 | |||
608 | def refresh(self, sleeptime): | 699 | def refresh(self, sleeptime): |
609 | self.screen.erase() | 700 | self.screen.erase() |
610 | self.screen.addstr(0, 0, 'kvm statistics - summary', curses.A_BOLD) | 701 | if self.stats.pid_filter > 0: |
702 | self.screen.addstr(0, 0, 'kvm statistics - pid {0}' | ||
703 | .format(self.stats.pid_filter), | ||
704 | curses.A_BOLD) | ||
705 | else: | ||
706 | self.screen.addstr(0, 0, 'kvm statistics - summary', curses.A_BOLD) | ||
611 | self.screen.addstr(2, 1, 'Event') | 707 | self.screen.addstr(2, 1, 'Event') |
612 | self.screen.addstr(2, 1 + LABEL_WIDTH + NUMBER_WIDTH - | 708 | self.screen.addstr(2, 1 + LABEL_WIDTH + NUMBER_WIDTH - |
613 | len('Total'), 'Total') | 709 | len('Total'), 'Total') |
@@ -659,6 +755,37 @@ class Tui(object): | |||
659 | except re.error: | 755 | except re.error: |
660 | continue | 756 | continue |
661 | 757 | ||
758 | def show_vm_selection(self): | ||
759 | while True: | ||
760 | self.screen.erase() | ||
761 | self.screen.addstr(0, 0, | ||
762 | 'Show statistics for specific pid.', | ||
763 | curses.A_BOLD) | ||
764 | self.screen.addstr(1, 0, | ||
765 | 'This might limit the shown data to the trace ' | ||
766 | 'statistics.') | ||
767 | |||
768 | curses.echo() | ||
769 | self.screen.addstr(3, 0, "Pid [0 or pid]: ") | ||
770 | pid = self.screen.getstr() | ||
771 | curses.noecho() | ||
772 | |||
773 | try: | ||
774 | pid = int(pid) | ||
775 | |||
776 | if pid == 0: | ||
777 | self.update_pid(pid) | ||
778 | break | ||
779 | else: | ||
780 | if not os.path.isdir(os.path.join('/proc/', str(pid))): | ||
781 | continue | ||
782 | else: | ||
783 | self.update_pid(pid) | ||
784 | break | ||
785 | |||
786 | except ValueError: | ||
787 | continue | ||
788 | |||
662 | def show_stats(self): | 789 | def show_stats(self): |
663 | sleeptime = 0.25 | 790 | sleeptime = 0.25 |
664 | while True: | 791 | while True: |
@@ -674,6 +801,8 @@ class Tui(object): | |||
674 | break | 801 | break |
675 | if char == 'f': | 802 | if char == 'f': |
676 | self.show_filter_selection() | 803 | self.show_filter_selection() |
804 | if char == 'p': | ||
805 | self.show_vm_selection() | ||
677 | except KeyboardInterrupt: | 806 | except KeyboardInterrupt: |
678 | break | 807 | break |
679 | except curses.error: | 808 | except curses.error: |
@@ -766,6 +895,13 @@ Requirements: | |||
766 | dest='fields', | 895 | dest='fields', |
767 | help='fields to display (regex)', | 896 | help='fields to display (regex)', |
768 | ) | 897 | ) |
898 | optparser.add_option('-p', '--pid', | ||
899 | action='store', | ||
900 | default=0, | ||
901 | type=int, | ||
902 | dest='pid', | ||
903 | help='restrict statistics to pid', | ||
904 | ) | ||
769 | (options, _) = optparser.parse_args(sys.argv) | 905 | (options, _) = optparser.parse_args(sys.argv) |
770 | return options | 906 | return options |
771 | 907 | ||
@@ -812,8 +948,15 @@ def check_access(options): | |||
812 | def main(): | 948 | def main(): |
813 | options = get_options() | 949 | options = get_options() |
814 | options = check_access(options) | 950 | options = check_access(options) |
951 | |||
952 | if (options.pid > 0 and | ||
953 | not os.path.isdir(os.path.join('/proc/', | ||
954 | str(options.pid)))): | ||
955 | sys.stderr.write('Did you use a (unsupported) tid instead of a pid?\n') | ||
956 | sys.exit('Specified pid does not exist.') | ||
957 | |||
815 | providers = get_providers(options) | 958 | providers = get_providers(options) |
816 | stats = Stats(providers, fields=options.fields) | 959 | stats = Stats(providers, options.pid, fields=options.fields) |
817 | 960 | ||
818 | if options.log: | 961 | if options.log: |
819 | log(stats) | 962 | log(stats) |
diff --git a/tools/kvm/kvm_stat/kvm_stat.txt b/tools/kvm/kvm_stat/kvm_stat.txt index 8dcb48aaca50..b92a153d7115 100644 --- a/tools/kvm/kvm_stat/kvm_stat.txt +++ b/tools/kvm/kvm_stat/kvm_stat.txt | |||
@@ -23,8 +23,6 @@ The set of KVM kernel module trace events may be specific to the kernel version | |||
23 | or architecture. It is best to check the KVM kernel module source code for the | 23 | or architecture. It is best to check the KVM kernel module source code for the |
24 | meaning of events. | 24 | meaning of events. |
25 | 25 | ||
26 | Note that trace events are counted globally across all running guests. | ||
27 | |||
28 | OPTIONS | 26 | OPTIONS |
29 | ------- | 27 | ------- |
30 | -1:: | 28 | -1:: |
@@ -44,6 +42,10 @@ OPTIONS | |||
44 | --debugfs:: | 42 | --debugfs:: |
45 | retrieve statistics from debugfs | 43 | retrieve statistics from debugfs |
46 | 44 | ||
45 | -p<pid>:: | ||
46 | --pid=<pid>:: | ||
47 | limit statistics to one virtual machine (pid) | ||
48 | |||
47 | -f<fields>:: | 49 | -f<fields>:: |
48 | --fields=<fields>:: | 50 | --fields=<fields>:: |
49 | fields to display (regex) | 51 | fields to display (regex) |