3 # top-like utility for displaying kvm statistics
5 # Copyright 2006-2008 Qumranet Technologies
6 # Copyright 2008-2011 Red Hat, Inc.
9 # Avi Kivity <avi@redhat.com>
11 # This work is licensed under the terms of the GNU GPL, version 2. See
12 # the COPYING file in the top-level directory.
24 from collections
import defaultdict
28 'EXTERNAL_INTERRUPT': 1,
30 'PENDING_INTERRUPT': 7,
54 'MWAIT_INSTRUCTION': 36,
55 'MONITOR_INSTRUCTION': 39,
56 'PAUSE_INSTRUCTION': 40,
57 'MCE_DURING_VMENTRY': 41,
58 'TPR_BELOW_THRESHOLD': 43,
99 'CR0_SEL_WRITE': 0x065,
123 'TASK_SWITCH': 0x07d,
124 'FERR_FREEZE': 0x07e,
143 # EC definition of HSR (from arch/arm64/include/asm/kvm_arm.h)
144 AARCH64_EXIT_REASONS
= {
182 # From include/uapi/linux/kvm.h, KVM_EXIT_xxx
183 USERSPACE_EXIT_REASONS
= {
191 'IRQ_WINDOW_OPEN': 7,
201 'INTERNAL_ERROR': 17,
212 'SET_FILTER': 0x40082406,
213 'ENABLE': 0x00002400,
214 'DISABLE': 0x00002401,
219 """Class that encapsulates global architecture specific data like
220 syscall and ioctl numbers.
225 machine
= os
.uname()[4]
227 if machine
.startswith('ppc'):
229 elif machine
.startswith('aarch64'):
231 elif machine
.startswith('s390'):
235 for line
in open('/proc/cpuinfo'):
236 if not line
.startswith('flags'):
241 return ArchX86(VMX_EXIT_REASONS
)
243 return ArchX86(SVM_EXIT_REASONS
)
247 def __init__(self
, exit_reasons
):
248 self
.sc_perf_evt_open
= 298
249 self
.ioctl_numbers
= IOCTL_NUMBERS
250 self
.exit_reasons
= exit_reasons
254 self
.sc_perf_evt_open
= 319
255 self
.ioctl_numbers
= IOCTL_NUMBERS
256 self
.ioctl_numbers
['ENABLE'] = 0x20002400
257 self
.ioctl_numbers
['DISABLE'] = 0x20002401
259 # PPC comes in 32 and 64 bit and some generated ioctl
260 # numbers depend on the wordsize.
261 char_ptr_size
= ctypes
.sizeof(ctypes
.c_char_p
)
262 self
.ioctl_numbers
['SET_FILTER'] = 0x80002406 | char_ptr_size
<< 16
266 self
.sc_perf_evt_open
= 241
267 self
.ioctl_numbers
= IOCTL_NUMBERS
268 self
.exit_reasons
= AARCH64_EXIT_REASONS
270 class ArchS390(Arch
):
272 self
.sc_perf_evt_open
= 331
273 self
.ioctl_numbers
= IOCTL_NUMBERS
274 self
.exit_reasons
= None
276 ARCH
= Arch
.get_arch()
280 """Returns os.walk() data for specified directory.
282 As it is only a wrapper it returns the same 3-tuple of (dirpath,
283 dirnames, filenames).
285 return next(os
.walk(path
))
288 def parse_int_list(list_string
):
289 """Returns an int list from a string of comma separated integers and
292 members
= list_string
.split(',')
294 for member
in members
:
295 if '-' not in member
:
296 integers
.append(int(member
))
298 int_range
= member
.split('-')
299 integers
.extend(range(int(int_range
[0]),
300 int(int_range
[1]) + 1))
305 def get_online_cpus():
306 with
open('/sys/devices/system/cpu/online') as cpu_list
:
307 cpu_string
= cpu_list
.readline()
308 return parse_int_list(cpu_string
)
313 filters
['kvm_userspace_exit'] = ('reason', USERSPACE_EXIT_REASONS
)
314 if ARCH
.exit_reasons
:
315 filters
['kvm_exit'] = ('exit_reason', ARCH
.exit_reasons
)
318 libc
= ctypes
.CDLL('libc.so.6', use_errno
=True)
319 syscall
= libc
.syscall
321 class perf_event_attr(ctypes
.Structure
):
322 _fields_
= [('type', ctypes
.c_uint32
),
323 ('size', ctypes
.c_uint32
),
324 ('config', ctypes
.c_uint64
),
325 ('sample_freq', ctypes
.c_uint64
),
326 ('sample_type', ctypes
.c_uint64
),
327 ('read_format', ctypes
.c_uint64
),
328 ('flags', ctypes
.c_uint64
),
329 ('wakeup_events', ctypes
.c_uint32
),
330 ('bp_type', ctypes
.c_uint32
),
331 ('bp_addr', ctypes
.c_uint64
),
332 ('bp_len', ctypes
.c_uint64
),
336 super(self
.__class
__, self
).__init
__()
337 self
.type = PERF_TYPE_TRACEPOINT
338 self
.size
= ctypes
.sizeof(self
)
339 self
.read_format
= PERF_FORMAT_GROUP
341 def perf_event_open(attr
, pid
, cpu
, group_fd
, flags
):
342 return syscall(ARCH
.sc_perf_evt_open
, ctypes
.pointer(attr
),
343 ctypes
.c_int(pid
), ctypes
.c_int(cpu
),
344 ctypes
.c_int(group_fd
), ctypes
.c_long(flags
))
346 PERF_TYPE_TRACEPOINT
= 2
347 PERF_FORMAT_GROUP
= 1 << 3
349 PATH_DEBUGFS_TRACING
= '/sys/kernel/debug/tracing'
350 PATH_DEBUGFS_KVM
= '/sys/kernel/debug/kvm'
356 def add_event(self
, event
):
357 self
.events
.append(event
)
360 length
= 8 * (1 + len(self
.events
))
361 read_format
= 'xxxxxxxx' + 'Q' * len(self
.events
)
362 return dict(zip([event
.name
for event
in self
.events
],
363 struct
.unpack(read_format
,
364 os
.read(self
.events
[0].fd
, length
))))
367 def __init__(self
, name
, group
, trace_cpu
, trace_point
, trace_filter
,
371 self
.setup_event(group
, trace_cpu
, trace_point
, trace_filter
,
374 def setup_event_attribute(self
, trace_set
, trace_point
):
375 id_path
= os
.path
.join(PATH_DEBUGFS_TRACING
, 'events', trace_set
,
378 event_attr
= perf_event_attr()
379 event_attr
.config
= int(open(id_path
).read())
382 def setup_event(self
, group
, trace_cpu
, trace_point
, trace_filter
,
384 event_attr
= self
.setup_event_attribute(trace_set
, trace_point
)
388 group_leader
= group
.events
[0].fd
390 fd
= perf_event_open(event_attr
, -1, trace_cpu
,
393 err
= ctypes
.get_errno()
394 raise OSError(err
, os
.strerror(err
),
395 'while calling sys_perf_event_open().')
398 fcntl
.ioctl(fd
, ARCH
.ioctl_numbers
['SET_FILTER'],
404 fcntl
.ioctl(self
.fd
, ARCH
.ioctl_numbers
['ENABLE'], 0)
407 fcntl
.ioctl(self
.fd
, ARCH
.ioctl_numbers
['DISABLE'], 0)
410 fcntl
.ioctl(self
.fd
, ARCH
.ioctl_numbers
['RESET'], 0)
412 class TracepointProvider(object):
414 self
.group_leaders
= []
415 self
.filters
= get_filters()
416 self
._fields
= self
.get_available_fields()
418 self
.fields
= self
._fields
420 def get_available_fields(self
):
421 path
= os
.path
.join(PATH_DEBUGFS_TRACING
, 'events', 'kvm')
422 fields
= walkdir(path
)[1]
425 if field
in self
.filters
:
426 filter_name_
, filter_dicts
= self
.filters
[field
]
427 for name
in filter_dicts
:
428 extra
.append(field
+ '(' + name
+ ')')
432 def setup_traces(self
):
433 cpus
= get_online_cpus()
435 # The constant is needed as a buffer for python libs, std
436 # streams and other files that the script opens.
437 newlim
= len(cpus
) * len(self
._fields
) + 50
439 softlim_
, hardlim
= resource
.getrlimit(resource
.RLIMIT_NOFILE
)
442 # Now we need CAP_SYS_RESOURCE, to increase the hard limit.
443 resource
.setrlimit(resource
.RLIMIT_NOFILE
, (newlim
, newlim
))
445 # Raising the soft limit is sufficient.
446 resource
.setrlimit(resource
.RLIMIT_NOFILE
, (newlim
, hardlim
))
449 sys
.exit("NOFILE rlimit could not be raised to {0}".format(newlim
))
453 for name
in self
._fields
:
456 match
= re
.match(r
'(.*)\((.*)\)', name
)
458 tracepoint
, sub
= match
.groups()
459 tracefilter
= ('%s==%d\0' %
460 (self
.filters
[tracepoint
][0],
461 self
.filters
[tracepoint
][1][sub
]))
463 group
.add_event(Event(name
=name
,
466 trace_point
=tracepoint
,
467 trace_filter
=tracefilter
))
468 self
.group_leaders
.append(group
)
470 def available_fields(self
):
471 return self
.get_available_fields()
478 def fields(self
, fields
):
479 self
._fields
= fields
480 for group
in self
.group_leaders
:
481 for index
, event
in enumerate(group
.events
):
482 if event
.name
in fields
:
486 # Do not disable the group leader.
487 # It would disable all of its events.
492 ret
= defaultdict(int)
493 for group
in self
.group_leaders
:
494 for name
, val
in group
.read().iteritems():
495 if name
in self
._fields
:
499 class DebugfsProvider(object):
501 self
._fields
= self
.get_available_fields()
503 def get_available_fields(self
):
504 return walkdir(PATH_DEBUGFS_KVM
)[2]
511 def fields(self
, fields
):
512 self
._fields
= fields
516 return int(file(PATH_DEBUGFS_KVM
+ '/' + key
).read())
517 return dict([(key
, val(key
)) for key
in self
._fields
])
520 def __init__(self
, providers
, fields
=None):
521 self
.providers
= providers
522 self
._fields
_filter
= fields
524 self
.update_provider_filters()
526 def update_provider_filters(self
):
528 if not self
._fields
_filter
:
530 return re
.match(self
._fields
_filter
, key
) is not None
532 # As we reset the counters when updating the fields we can
533 # also clear the cache of old values.
535 for provider
in self
.providers
:
536 provider_fields
= [key
for key
in provider
.get_available_fields()
538 provider
.fields
= provider_fields
541 def fields_filter(self
):
542 return self
._fields
_filter
544 @fields_filter.setter
545 def fields_filter(self
, fields_filter
):
546 self
._fields
_filter
= fields_filter
547 self
.update_provider_filters()
550 for provider
in self
.providers
:
551 new
= provider
.read()
552 for key
in provider
.fields
:
553 oldval
= self
.values
.get(key
, (0, 0))
554 newval
= new
.get(key
, 0)
556 if oldval
is not None:
557 newdelta
= newval
- oldval
[0]
558 self
.values
[key
] = (newval
, newdelta
)
565 def __init__(self
, stats
):
568 self
.drilldown
= False
569 self
.update_drilldown()
572 """Initialises curses for later use. Based on curses.wrapper
573 implementation from the Python standard library."""
574 self
.screen
= curses
.initscr()
578 # The try/catch works around a minor bit of
579 # over-conscientiousness in the curses module, the error
580 # return from C start_color() is ignorable.
586 curses
.use_default_colors()
589 def __exit__(self
, *exception
):
590 """Resets the terminal to its normal state. Based on curses.wrappre
591 implementation from the Python standard library."""
593 self
.screen
.keypad(0)
598 def update_drilldown(self
):
599 if not self
.stats
.fields_filter
:
600 self
.stats
.fields_filter
= r
'^[^\(]*$'
602 elif self
.stats
.fields_filter
== r
'^[^\(]*$':
603 self
.stats
.fields_filter
= None
605 def refresh(self
, sleeptime
):
607 self
.screen
.addstr(0, 0, 'kvm statistics - summary', curses
.A_BOLD
)
608 self
.screen
.addstr(2, 1, 'Event')
609 self
.screen
.addstr(2, 1 + LABEL_WIDTH
+ NUMBER_WIDTH
-
610 len('Total'), 'Total')
611 self
.screen
.addstr(2, 1 + LABEL_WIDTH
+ NUMBER_WIDTH
+ 8 -
612 len('Current'), 'Current')
614 stats
= self
.stats
.get()
617 return (-stats
[x
][1], -stats
[x
][0])
619 return (0, -stats
[x
][0])
620 for key
in sorted(stats
.keys(), key
=sortkey
):
622 if row
>= self
.screen
.getmaxyx()[0]:
625 if not values
[0] and not values
[1]:
628 self
.screen
.addstr(row
, col
, key
)
630 self
.screen
.addstr(row
, col
, '%10d' % (values
[0],))
632 if values
[1] is not None:
633 self
.screen
.addstr(row
, col
, '%8d' % (values
[1] / sleeptime
,))
635 self
.screen
.refresh()
637 def show_filter_selection(self
):
640 self
.screen
.addstr(0, 0,
641 "Show statistics for events matching a regex.",
643 self
.screen
.addstr(2, 0,
645 .format(self
.stats
.fields_filter
))
646 self
.screen
.addstr(3, 0, "New regex: ")
648 regex
= self
.screen
.getstr()
654 self
.stats
.fields_filter
= regex
659 def show_stats(self
):
662 self
.refresh(sleeptime
)
663 curses
.halfdelay(int(sleeptime
* 10))
666 char
= self
.screen
.getkey()
668 self
.drilldown
= not self
.drilldown
669 self
.update_drilldown()
673 self
.show_filter_selection()
674 except KeyboardInterrupt:
683 for key
in sorted(s
.keys()):
685 print '%-42s%10d%10d' % (key
, values
[0], values
[1])
688 keys
= sorted(stats
.get().iterkeys())
696 print ' %9d' % s
[k
][1],
702 if line
% banner_repeat
== 0:
708 description_text
= """
709 This script displays various statistics about VMs running under KVM.
710 The statistics are gathered from the KVM debugfs entries and / or the
711 currently available perf traces.
713 The monitoring takes additional cpu cycles and might affect the VM's
718 /sys/kernel/debug/kvm
719 /sys/kernel/debug/trace/events/*
721 - /proc/sys/kernel/perf_event_paranoid < 1 if user has no
722 CAP_SYS_ADMIN and perf events are used.
723 - CAP_SYS_RESOURCE if the hard limit is not high enough to allow
724 the large number of files that are possibly opened.
727 class PlainHelpFormatter(optparse
.IndentedHelpFormatter
):
728 def format_description(self
, description
):
730 return description
+ "\n"
734 optparser
= optparse
.OptionParser(description
=description_text
,
735 formatter
=PlainHelpFormatter())
736 optparser
.add_option('-1', '--once', '--batch',
740 help='run in batch mode for one second',
742 optparser
.add_option('-l', '--log',
746 help='run in logging mode (like vmstat)',
748 optparser
.add_option('-t', '--tracepoints',
752 help='retrieve statistics from tracepoints',
754 optparser
.add_option('-d', '--debugfs',
758 help='retrieve statistics from debugfs',
760 optparser
.add_option('-f', '--fields',
764 help='fields to display (regex)',
766 (options
, _
) = optparser
.parse_args(sys
.argv
)
769 def get_providers(options
):
772 if options
.tracepoints
:
773 providers
.append(TracepointProvider())
775 providers
.append(DebugfsProvider())
776 if len(providers
) == 0:
777 providers
.append(TracepointProvider())
782 if not os
.path
.exists('/sys/kernel/debug'):
783 sys
.stderr
.write('Please enable CONFIG_DEBUG_FS in your kernel.')
786 if not os
.path
.exists(PATH_DEBUGFS_KVM
):
787 sys
.stderr
.write("Please make sure, that debugfs is mounted and "
788 "readable by the current user:\n"
789 "('mount -t debugfs debugfs /sys/kernel/debug')\n"
790 "Also ensure, that the kvm modules are loaded.\n")
793 if not os
.path
.exists(PATH_DEBUGFS_TRACING
):
794 sys
.stderr
.write("Please make {0} readable by the current user.\n"
795 .format(PATH_DEBUGFS_TRACING
))
800 options
= get_options()
801 providers
= get_providers(options
)
802 stats
= Stats(providers
, fields
=options
.fields
)
806 elif not options
.once
:
807 with
Tui(stats
) as tui
:
812 if __name__
== "__main__":