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
25 from time
import sleep
29 'EXTERNAL_INTERRUPT': 1,
31 'PENDING_INTERRUPT': 7,
55 'MWAIT_INSTRUCTION': 36,
56 'MONITOR_INSTRUCTION': 39,
57 'PAUSE_INSTRUCTION': 40,
58 'MCE_DURING_VMENTRY': 41,
59 'TPR_BELOW_THRESHOLD': 43,
100 'CR0_SEL_WRITE': 0x065,
124 'TASK_SWITCH': 0x07d,
125 'FERR_FREEZE': 0x07e,
144 # EC definition of HSR (from arch/arm64/include/asm/kvm_arm.h)
145 AARCH64_EXIT_REASONS
= {
183 # From include/uapi/linux/kvm.h, KVM_EXIT_xxx
184 USERSPACE_EXIT_REASONS
= {
192 'IRQ_WINDOW_OPEN': 7,
202 'INTERNAL_ERROR': 17,
213 'SET_FILTER': 0x40082406,
214 'ENABLE': 0x00002400,
215 'DISABLE': 0x00002401,
220 """Class that encapsulates global architecture specific data like
221 syscall and ioctl numbers.
226 machine
= os
.uname()[4]
228 if machine
.startswith('ppc'):
230 elif machine
.startswith('aarch64'):
232 elif machine
.startswith('s390'):
236 for line
in open('/proc/cpuinfo'):
237 if not line
.startswith('flags'):
242 return ArchX86(VMX_EXIT_REASONS
)
244 return ArchX86(SVM_EXIT_REASONS
)
248 def __init__(self
, exit_reasons
):
249 self
.sc_perf_evt_open
= 298
250 self
.ioctl_numbers
= IOCTL_NUMBERS
251 self
.exit_reasons
= exit_reasons
255 self
.sc_perf_evt_open
= 319
256 self
.ioctl_numbers
= IOCTL_NUMBERS
257 self
.ioctl_numbers
['ENABLE'] = 0x20002400
258 self
.ioctl_numbers
['DISABLE'] = 0x20002401
260 # PPC comes in 32 and 64 bit and some generated ioctl
261 # numbers depend on the wordsize.
262 char_ptr_size
= ctypes
.sizeof(ctypes
.c_char_p
)
263 self
.ioctl_numbers
['SET_FILTER'] = 0x80002406 | char_ptr_size
<< 16
267 self
.sc_perf_evt_open
= 241
268 self
.ioctl_numbers
= IOCTL_NUMBERS
269 self
.exit_reasons
= AARCH64_EXIT_REASONS
271 class ArchS390(Arch
):
273 self
.sc_perf_evt_open
= 331
274 self
.ioctl_numbers
= IOCTL_NUMBERS
275 self
.exit_reasons
= None
277 ARCH
= Arch
.get_arch()
281 """Returns os.walk() data for specified directory.
283 As it is only a wrapper it returns the same 3-tuple of (dirpath,
284 dirnames, filenames).
286 return next(os
.walk(path
))
289 def parse_int_list(list_string
):
290 """Returns an int list from a string of comma separated integers and
293 members
= list_string
.split(',')
295 for member
in members
:
296 if '-' not in member
:
297 integers
.append(int(member
))
299 int_range
= member
.split('-')
300 integers
.extend(range(int(int_range
[0]),
301 int(int_range
[1]) + 1))
306 def get_online_cpus():
307 with
open('/sys/devices/system/cpu/online') as cpu_list
:
308 cpu_string
= cpu_list
.readline()
309 return parse_int_list(cpu_string
)
314 filters
['kvm_userspace_exit'] = ('reason', USERSPACE_EXIT_REASONS
)
315 if ARCH
.exit_reasons
:
316 filters
['kvm_exit'] = ('exit_reason', ARCH
.exit_reasons
)
319 libc
= ctypes
.CDLL('libc.so.6', use_errno
=True)
320 syscall
= libc
.syscall
322 class perf_event_attr(ctypes
.Structure
):
323 _fields_
= [('type', ctypes
.c_uint32
),
324 ('size', ctypes
.c_uint32
),
325 ('config', ctypes
.c_uint64
),
326 ('sample_freq', ctypes
.c_uint64
),
327 ('sample_type', ctypes
.c_uint64
),
328 ('read_format', ctypes
.c_uint64
),
329 ('flags', ctypes
.c_uint64
),
330 ('wakeup_events', ctypes
.c_uint32
),
331 ('bp_type', ctypes
.c_uint32
),
332 ('bp_addr', ctypes
.c_uint64
),
333 ('bp_len', ctypes
.c_uint64
),
337 super(self
.__class
__, self
).__init
__()
338 self
.type = PERF_TYPE_TRACEPOINT
339 self
.size
= ctypes
.sizeof(self
)
340 self
.read_format
= PERF_FORMAT_GROUP
342 def perf_event_open(attr
, pid
, cpu
, group_fd
, flags
):
343 return syscall(ARCH
.sc_perf_evt_open
, ctypes
.pointer(attr
),
344 ctypes
.c_int(pid
), ctypes
.c_int(cpu
),
345 ctypes
.c_int(group_fd
), ctypes
.c_long(flags
))
347 PERF_TYPE_TRACEPOINT
= 2
348 PERF_FORMAT_GROUP
= 1 << 3
350 PATH_DEBUGFS_TRACING
= '/sys/kernel/debug/tracing'
351 PATH_DEBUGFS_KVM
= '/sys/kernel/debug/kvm'
357 def add_event(self
, event
):
358 self
.events
.append(event
)
361 length
= 8 * (1 + len(self
.events
))
362 read_format
= 'xxxxxxxx' + 'Q' * len(self
.events
)
363 return dict(zip([event
.name
for event
in self
.events
],
364 struct
.unpack(read_format
,
365 os
.read(self
.events
[0].fd
, length
))))
368 def __init__(self
, name
, group
, trace_cpu
, trace_point
, trace_filter
,
372 self
.setup_event(group
, trace_cpu
, trace_point
, trace_filter
,
375 def setup_event_attribute(self
, trace_set
, trace_point
):
376 id_path
= os
.path
.join(PATH_DEBUGFS_TRACING
, 'events', trace_set
,
379 event_attr
= perf_event_attr()
380 event_attr
.config
= int(open(id_path
).read())
383 def setup_event(self
, group
, trace_cpu
, trace_point
, trace_filter
,
385 event_attr
= self
.setup_event_attribute(trace_set
, trace_point
)
389 group_leader
= group
.events
[0].fd
391 fd
= perf_event_open(event_attr
, -1, trace_cpu
,
394 err
= ctypes
.get_errno()
395 raise OSError(err
, os
.strerror(err
),
396 'while calling sys_perf_event_open().')
399 fcntl
.ioctl(fd
, ARCH
.ioctl_numbers
['SET_FILTER'],
405 fcntl
.ioctl(self
.fd
, ARCH
.ioctl_numbers
['ENABLE'], 0)
408 fcntl
.ioctl(self
.fd
, ARCH
.ioctl_numbers
['DISABLE'], 0)
411 fcntl
.ioctl(self
.fd
, ARCH
.ioctl_numbers
['RESET'], 0)
413 class TracepointProvider(object):
415 self
.group_leaders
= []
416 self
.filters
= get_filters()
417 self
._fields
= self
.get_available_fields()
419 self
.fields
= self
._fields
421 def get_available_fields(self
):
422 path
= os
.path
.join(PATH_DEBUGFS_TRACING
, 'events', 'kvm')
423 fields
= walkdir(path
)[1]
426 if field
in self
.filters
:
427 filter_name_
, filter_dicts
= self
.filters
[field
]
428 for name
in filter_dicts
:
429 extra
.append(field
+ '(' + name
+ ')')
433 def setup_traces(self
):
434 cpus
= get_online_cpus()
436 # The constant is needed as a buffer for python libs, std
437 # streams and other files that the script opens.
438 newlim
= len(cpus
) * len(self
._fields
) + 50
440 softlim_
, hardlim
= resource
.getrlimit(resource
.RLIMIT_NOFILE
)
443 # Now we need CAP_SYS_RESOURCE, to increase the hard limit.
444 resource
.setrlimit(resource
.RLIMIT_NOFILE
, (newlim
, newlim
))
446 # Raising the soft limit is sufficient.
447 resource
.setrlimit(resource
.RLIMIT_NOFILE
, (newlim
, hardlim
))
450 sys
.exit("NOFILE rlimit could not be raised to {0}".format(newlim
))
454 for name
in self
._fields
:
457 match
= re
.match(r
'(.*)\((.*)\)', name
)
459 tracepoint
, sub
= match
.groups()
460 tracefilter
= ('%s==%d\0' %
461 (self
.filters
[tracepoint
][0],
462 self
.filters
[tracepoint
][1][sub
]))
464 group
.add_event(Event(name
=name
,
467 trace_point
=tracepoint
,
468 trace_filter
=tracefilter
))
469 self
.group_leaders
.append(group
)
471 def available_fields(self
):
472 return self
.get_available_fields()
479 def fields(self
, fields
):
480 self
._fields
= fields
481 for group
in self
.group_leaders
:
482 for index
, event
in enumerate(group
.events
):
483 if event
.name
in fields
:
487 # Do not disable the group leader.
488 # It would disable all of its events.
493 ret
= defaultdict(int)
494 for group
in self
.group_leaders
:
495 for name
, val
in group
.read().iteritems():
496 if name
in self
._fields
:
500 class DebugfsProvider(object):
502 self
._fields
= self
.get_available_fields()
504 def get_available_fields(self
):
505 return walkdir(PATH_DEBUGFS_KVM
)[2]
512 def fields(self
, fields
):
513 self
._fields
= fields
517 return int(file(PATH_DEBUGFS_KVM
+ '/' + key
).read())
518 return dict([(key
, val(key
)) for key
in self
._fields
])
521 def __init__(self
, providers
, fields
=None):
522 self
.providers
= providers
523 self
._fields
_filter
= fields
525 self
.update_provider_filters()
527 def update_provider_filters(self
):
529 if not self
._fields
_filter
:
531 return re
.match(self
._fields
_filter
, key
) is not None
533 # As we reset the counters when updating the fields we can
534 # also clear the cache of old values.
536 for provider
in self
.providers
:
537 provider_fields
= [key
for key
in provider
.get_available_fields()
539 provider
.fields
= provider_fields
542 def fields_filter(self
):
543 return self
._fields
_filter
545 @fields_filter.setter
546 def fields_filter(self
, fields_filter
):
547 self
._fields
_filter
= fields_filter
548 self
.update_provider_filters()
551 for provider
in self
.providers
:
552 new
= provider
.read()
553 for key
in provider
.fields
:
554 oldval
= self
.values
.get(key
, (0, 0))
555 newval
= new
.get(key
, 0)
557 if oldval
is not None:
558 newdelta
= newval
- oldval
[0]
559 self
.values
[key
] = (newval
, newdelta
)
566 def __init__(self
, stats
):
569 self
.drilldown
= False
570 self
.update_drilldown()
573 """Initialises curses for later use. Based on curses.wrapper
574 implementation from the Python standard library."""
575 self
.screen
= curses
.initscr()
579 # The try/catch works around a minor bit of
580 # over-conscientiousness in the curses module, the error
581 # return from C start_color() is ignorable.
587 curses
.use_default_colors()
590 def __exit__(self
, *exception
):
591 """Resets the terminal to its normal state. Based on curses.wrappre
592 implementation from the Python standard library."""
594 self
.screen
.keypad(0)
599 def update_drilldown(self
):
600 if not self
.stats
.fields_filter
:
601 self
.stats
.fields_filter
= r
'^[^\(]*$'
603 elif self
.stats
.fields_filter
== r
'^[^\(]*$':
604 self
.stats
.fields_filter
= None
606 def refresh(self
, sleeptime
):
608 self
.screen
.addstr(0, 0, 'kvm statistics - summary', curses
.A_BOLD
)
609 self
.screen
.addstr(2, 1, 'Event')
610 self
.screen
.addstr(2, 1 + LABEL_WIDTH
+ NUMBER_WIDTH
-
611 len('Total'), 'Total')
612 self
.screen
.addstr(2, 1 + LABEL_WIDTH
+ NUMBER_WIDTH
+ 8 -
613 len('Current'), 'Current')
615 stats
= self
.stats
.get()
618 return (-stats
[x
][1], -stats
[x
][0])
620 return (0, -stats
[x
][0])
621 for key
in sorted(stats
.keys(), key
=sortkey
):
623 if row
>= self
.screen
.getmaxyx()[0]:
626 if not values
[0] and not values
[1]:
629 self
.screen
.addstr(row
, col
, key
)
631 self
.screen
.addstr(row
, col
, '%10d' % (values
[0],))
633 if values
[1] is not None:
634 self
.screen
.addstr(row
, col
, '%8d' % (values
[1] / sleeptime
,))
636 self
.screen
.refresh()
638 def show_filter_selection(self
):
641 self
.screen
.addstr(0, 0,
642 "Show statistics for events matching a regex.",
644 self
.screen
.addstr(2, 0,
646 .format(self
.stats
.fields_filter
))
647 self
.screen
.addstr(3, 0, "New regex: ")
649 regex
= self
.screen
.getstr()
655 self
.stats
.fields_filter
= regex
660 def show_stats(self
):
663 self
.refresh(sleeptime
)
664 curses
.halfdelay(int(sleeptime
* 10))
667 char
= self
.screen
.getkey()
669 self
.drilldown
= not self
.drilldown
670 self
.update_drilldown()
674 self
.show_filter_selection()
675 except KeyboardInterrupt:
684 for key
in sorted(s
.keys()):
686 print '%-42s%10d%10d' % (key
, values
[0], values
[1])
689 keys
= sorted(stats
.get().iterkeys())
697 print ' %9d' % s
[k
][1],
703 if line
% banner_repeat
== 0:
709 description_text
= """
710 This script displays various statistics about VMs running under KVM.
711 The statistics are gathered from the KVM debugfs entries and / or the
712 currently available perf traces.
714 The monitoring takes additional cpu cycles and might affect the VM's
719 /sys/kernel/debug/kvm
720 /sys/kernel/debug/trace/events/*
722 - /proc/sys/kernel/perf_event_paranoid < 1 if user has no
723 CAP_SYS_ADMIN and perf events are used.
724 - CAP_SYS_RESOURCE if the hard limit is not high enough to allow
725 the large number of files that are possibly opened.
728 class PlainHelpFormatter(optparse
.IndentedHelpFormatter
):
729 def format_description(self
, description
):
731 return description
+ "\n"
735 optparser
= optparse
.OptionParser(description
=description_text
,
736 formatter
=PlainHelpFormatter())
737 optparser
.add_option('-1', '--once', '--batch',
741 help='run in batch mode for one second',
743 optparser
.add_option('-l', '--log',
747 help='run in logging mode (like vmstat)',
749 optparser
.add_option('-t', '--tracepoints',
753 help='retrieve statistics from tracepoints',
755 optparser
.add_option('-d', '--debugfs',
759 help='retrieve statistics from debugfs',
761 optparser
.add_option('-f', '--fields',
765 help='fields to display (regex)',
767 (options
, _
) = optparser
.parse_args(sys
.argv
)
770 def get_providers(options
):
773 if options
.tracepoints
:
774 providers
.append(TracepointProvider())
776 providers
.append(DebugfsProvider())
777 if len(providers
) == 0:
778 providers
.append(TracepointProvider())
782 def check_access(options
):
783 if not os
.path
.exists('/sys/kernel/debug'):
784 sys
.stderr
.write('Please enable CONFIG_DEBUG_FS in your kernel.')
787 if not os
.path
.exists(PATH_DEBUGFS_KVM
):
788 sys
.stderr
.write("Please make sure, that debugfs is mounted and "
789 "readable by the current user:\n"
790 "('mount -t debugfs debugfs /sys/kernel/debug')\n"
791 "Also ensure, that the kvm modules are loaded.\n")
794 if not os
.path
.exists(PATH_DEBUGFS_TRACING
) and (options
.tracepoints
795 or not options
.debugfs
):
796 sys
.stderr
.write("Please enable CONFIG_TRACING in your kernel "
797 "when using the option -t (default).\n"
798 "If it is enabled, make {0} readable by the "
800 .format(PATH_DEBUGFS_TRACING
))
801 if options
.tracepoints
:
804 sys
.stderr
.write("Falling back to debugfs statistics!\n")
805 options
.debugfs
= True
811 options
= get_options()
812 options
= check_access(options
)
813 providers
= get_providers(options
)
814 stats
= Stats(providers
, fields
=options
.fields
)
818 elif not options
.once
:
819 with
Tui(stats
) as tui
:
824 if __name__
== "__main__":