aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJanosch Frank <frankja@linux.vnet.ibm.com>2016-05-18 07:26:24 -0400
committerPaolo Bonzini <pbonzini@redhat.com>2016-05-25 10:12:06 -0400
commitf0cf040f842242d55744c2606e8b7177507fbbb0 (patch)
treef277b06f090e47953d50e88db8ec1d13d1a585bf
parent536a6f88c49dd739961ffd53774775afed852c83 (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-xtools/kvm/kvm_stat/kvm_stat183
-rw-r--r--tools/kvm/kvm_stat/kvm_stat.txt6
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
369class Event(object): 369class 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):
502class DebugfsProvider(object): 533class 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
522class Stats(object): 594class 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):
812def main(): 948def 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
23or architecture. It is best to check the KVM kernel module source code for the 23or architecture. It is best to check the KVM kernel module source code for the
24meaning of events. 24meaning of events.
25 25
26Note that trace events are counted globally across all running guests.
27
28OPTIONS 26OPTIONS
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)