]>
git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blob - tools/kvm/kvm_stat/kvm_stat
862c997932e2271f4c607c531988e61c86adfba9
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.
13 """The kvm_stat module outputs statistics about running KVM VMs
15 Three different ways of output formatting are available:
16 - as a top-like text ui
17 - in a key -> value format
18 - in an all keys, all values format
20 The data is sampled from the KVM's debugfs entries and its perf events.
22 from __future__
import print_function
36 from collections
import defaultdict
, namedtuple
40 'EXTERNAL_INTERRUPT': 1,
42 'PENDING_INTERRUPT': 7,
66 'MWAIT_INSTRUCTION': 36,
67 'MONITOR_INSTRUCTION': 39,
68 'PAUSE_INSTRUCTION': 40,
69 'MCE_DURING_VMENTRY': 41,
70 'TPR_BELOW_THRESHOLD': 43,
111 'CR0_SEL_WRITE': 0x065,
135 'TASK_SWITCH': 0x07d,
136 'FERR_FREEZE': 0x07e,
155 # EC definition of HSR (from arch/arm64/include/asm/kvm_arm.h)
156 AARCH64_EXIT_REASONS
= {
194 # From include/uapi/linux/kvm.h, KVM_EXIT_xxx
195 USERSPACE_EXIT_REASONS
= {
203 'IRQ_WINDOW_OPEN': 7,
213 'INTERNAL_ERROR': 17,
224 'SET_FILTER': 0x40082406,
225 'ENABLE': 0x00002400,
226 'DISABLE': 0x00002401,
230 ENCODING
= locale
.getpreferredencoding(False)
231 TRACE_FILTER
= re
.compile(r
'^[^\(]*$')
235 """Encapsulates global architecture specific data.
237 Contains the performance event open syscall and ioctl numbers, as
238 well as the VM exit reasons for the architecture it runs on.
243 machine
= os
.uname()[4]
245 if machine
.startswith('ppc'):
247 elif machine
.startswith('aarch64'):
249 elif machine
.startswith('s390'):
253 for line
in open('/proc/cpuinfo'):
254 if not line
.startswith('flags'):
259 return ArchX86(VMX_EXIT_REASONS
)
261 return ArchX86(SVM_EXIT_REASONS
)
264 def tracepoint_is_child(self
, field
):
265 if (TRACE_FILTER
.match(field
)):
267 return field
.split('(', 1)[0]
271 def __init__(self
, exit_reasons
):
272 self
.sc_perf_evt_open
= 298
273 self
.ioctl_numbers
= IOCTL_NUMBERS
274 self
.exit_reasons
= exit_reasons
276 def debugfs_is_child(self
, field
):
277 """ Returns name of parent if 'field' is a child, None otherwise """
283 self
.sc_perf_evt_open
= 319
284 self
.ioctl_numbers
= IOCTL_NUMBERS
285 self
.ioctl_numbers
['ENABLE'] = 0x20002400
286 self
.ioctl_numbers
['DISABLE'] = 0x20002401
287 self
.ioctl_numbers
['RESET'] = 0x20002403
289 # PPC comes in 32 and 64 bit and some generated ioctl
290 # numbers depend on the wordsize.
291 char_ptr_size
= ctypes
.sizeof(ctypes
.c_char_p
)
292 self
.ioctl_numbers
['SET_FILTER'] = 0x80002406 | char_ptr_size
<< 16
293 self
.exit_reasons
= {}
295 def debugfs_is_child(self
, field
):
296 """ Returns name of parent if 'field' is a child, None otherwise """
302 self
.sc_perf_evt_open
= 241
303 self
.ioctl_numbers
= IOCTL_NUMBERS
304 self
.exit_reasons
= AARCH64_EXIT_REASONS
306 def debugfs_is_child(self
, field
):
307 """ Returns name of parent if 'field' is a child, None otherwise """
311 class ArchS390(Arch
):
313 self
.sc_perf_evt_open
= 331
314 self
.ioctl_numbers
= IOCTL_NUMBERS
315 self
.exit_reasons
= None
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'
323 ARCH
= Arch
.get_arch()
326 class perf_event_attr(ctypes
.Structure
):
327 """Struct that holds the necessary data to set up a trace event.
329 For an extensive explanation see perf_event_open(2) and
330 include/uapi/linux/perf_event.h, struct perf_event_attr
332 All fields that are not initialized in the constructor are 0.
335 _fields_
= [('type', ctypes
.c_uint32
),
336 ('size', ctypes
.c_uint32
),
337 ('config', ctypes
.c_uint64
),
338 ('sample_freq', ctypes
.c_uint64
),
339 ('sample_type', ctypes
.c_uint64
),
340 ('read_format', ctypes
.c_uint64
),
341 ('flags', ctypes
.c_uint64
),
342 ('wakeup_events', ctypes
.c_uint32
),
343 ('bp_type', ctypes
.c_uint32
),
344 ('bp_addr', ctypes
.c_uint64
),
345 ('bp_len', ctypes
.c_uint64
),
349 super(self
.__class
__, self
).__init
__()
350 self
.type = PERF_TYPE_TRACEPOINT
351 self
.size
= ctypes
.sizeof(self
)
352 self
.read_format
= PERF_FORMAT_GROUP
355 PERF_TYPE_TRACEPOINT
= 2
356 PERF_FORMAT_GROUP
= 1 << 3
360 """Represents a perf event group."""
365 def add_event(self
, event
):
366 self
.events
.append(event
)
369 """Returns a dict with 'event name: value' for all events in the
372 Values are read by reading from the file descriptor of the
373 event that is the group leader. See perf_event_open(2) for
376 Read format for the used event configuration is:
378 u64 nr; /* The number of events */
380 u64 value; /* The value of the event */
385 length
= 8 * (1 + len(self
.events
))
386 read_format
= 'xxxxxxxx' + 'Q' * len(self
.events
)
387 return dict(zip([event
.name
for event
in self
.events
],
388 struct
.unpack(read_format
,
389 os
.read(self
.events
[0].fd
, length
))))
393 """Represents a performance event and manages its life cycle."""
394 def __init__(self
, name
, group
, trace_cpu
, trace_pid
, trace_point
,
395 trace_filter
, trace_set
='kvm'):
396 self
.libc
= ctypes
.CDLL('libc.so.6', use_errno
=True)
397 self
.syscall
= self
.libc
.syscall
400 self
._setup
_event
(group
, trace_cpu
, trace_pid
, trace_point
,
401 trace_filter
, trace_set
)
404 """Closes the event's file descriptor.
406 As no python file object was created for the file descriptor,
407 python will not reference count the descriptor and will not
408 close it itself automatically, so we do it.
414 def _perf_event_open(self
, attr
, pid
, cpu
, group_fd
, flags
):
415 """Wrapper for the sys_perf_evt_open() syscall.
417 Used to set up performance events, returns a file descriptor or -1
422 - struct perf_event_attr *
423 - pid or -1 to monitor all pids
424 - cpu number or -1 to monitor all cpus
425 - The file descriptor of the group leader or -1 to create a group.
429 return self
.syscall(ARCH
.sc_perf_evt_open
, ctypes
.pointer(attr
),
430 ctypes
.c_int(pid
), ctypes
.c_int(cpu
),
431 ctypes
.c_int(group_fd
), ctypes
.c_long(flags
))
433 def _setup_event_attribute(self
, trace_set
, trace_point
):
434 """Returns an initialized ctype perf_event_attr struct."""
436 id_path
= os
.path
.join(PATH_DEBUGFS_TRACING
, 'events', trace_set
,
439 event_attr
= perf_event_attr()
440 event_attr
.config
= int(open(id_path
).read())
443 def _setup_event(self
, group
, trace_cpu
, trace_pid
, trace_point
,
444 trace_filter
, trace_set
):
445 """Sets up the perf event in Linux.
447 Issues the syscall to register the event in the kernel and
448 then sets the optional filter.
452 event_attr
= self
._setup
_event
_attribute
(trace_set
, trace_point
)
454 # First event will be group leader.
457 # All others have to pass the leader's descriptor instead.
459 group_leader
= group
.events
[0].fd
461 fd
= self
._perf
_event
_open
(event_attr
, trace_pid
,
462 trace_cpu
, group_leader
, 0)
464 err
= ctypes
.get_errno()
465 raise OSError(err
, os
.strerror(err
),
466 'while calling sys_perf_event_open().')
469 fcntl
.ioctl(fd
, ARCH
.ioctl_numbers
['SET_FILTER'],
475 """Enables the trace event in the kernel.
477 Enabling the group leader makes reading counters from it and the
478 events under it possible.
481 fcntl
.ioctl(self
.fd
, ARCH
.ioctl_numbers
['ENABLE'], 0)
484 """Disables the trace event in the kernel.
486 Disabling the group leader makes reading all counters under it
490 fcntl
.ioctl(self
.fd
, ARCH
.ioctl_numbers
['DISABLE'], 0)
493 """Resets the count of the trace event in the kernel."""
494 fcntl
.ioctl(self
.fd
, ARCH
.ioctl_numbers
['RESET'], 0)
497 class Provider(object):
498 """Encapsulates functionalities used by all providers."""
499 def __init__(self
, pid
):
500 self
.child_events
= False
504 def is_field_wanted(fields_filter
, field
):
505 """Indicate whether field is valid according to fields_filter."""
506 if not fields_filter
:
508 return re
.match(fields_filter
, field
) is not None
512 """Returns os.walk() data for specified directory.
514 As it is only a wrapper it returns the same 3-tuple of (dirpath,
515 dirnames, filenames).
517 return next(os
.walk(path
))
520 class TracepointProvider(Provider
):
521 """Data provider for the stats class.
523 Manages the events/groups from which it acquires its data.
526 def __init__(self
, pid
, fields_filter
):
527 self
.group_leaders
= []
528 self
.filters
= self
._get
_filters
()
529 self
.update_fields(fields_filter
)
530 super(TracepointProvider
, self
).__init
__(pid
)
534 """Returns a dict of trace events, their filter ids and
535 the values that can be filtered.
537 Trace events can be filtered for special values by setting a
538 filter string via an ioctl. The string normally has the format
539 identifier==value. For each filter a new event will be created, to
540 be able to distinguish the events.
544 filters
['kvm_userspace_exit'] = ('reason', USERSPACE_EXIT_REASONS
)
545 if ARCH
.exit_reasons
:
546 filters
['kvm_exit'] = ('exit_reason', ARCH
.exit_reasons
)
549 def _get_available_fields(self
):
550 """Returns a list of available events of format 'event name(filter
553 All available events have directories under
554 /sys/kernel/debug/tracing/events/ which export information
555 about the specific event. Therefore, listing the dirs gives us
556 a list of all available events.
558 Some events like the vm exit reasons can be filtered for
559 specific values. To take account for that, the routine below
560 creates special fields with the following format:
561 event name(filter name)
564 path
= os
.path
.join(PATH_DEBUGFS_TRACING
, 'events', 'kvm')
565 fields
= self
.walkdir(path
)[1]
568 if field
in self
.filters
:
569 filter_name_
, filter_dicts
= self
.filters
[field
]
570 for name
in filter_dicts
:
571 extra
.append(field
+ '(' + name
+ ')')
575 def update_fields(self
, fields_filter
):
576 """Refresh fields, applying fields_filter"""
577 self
.fields
= [field
for field
in self
._get
_available
_fields
()
578 if self
.is_field_wanted(fields_filter
, field
) or
579 ARCH
.tracepoint_is_child(field
)]
582 def _get_online_cpus():
583 """Returns a list of cpu id integers."""
584 def parse_int_list(list_string
):
585 """Returns an int list from a string of comma separated integers and
588 members
= list_string
.split(',')
590 for member
in members
:
591 if '-' not in member
:
592 integers
.append(int(member
))
594 int_range
= member
.split('-')
595 integers
.extend(range(int(int_range
[0]),
596 int(int_range
[1]) + 1))
600 with
open('/sys/devices/system/cpu/online') as cpu_list
:
601 cpu_string
= cpu_list
.readline()
602 return parse_int_list(cpu_string
)
604 def _setup_traces(self
):
605 """Creates all event and group objects needed to be able to retrieve
607 fields
= self
._get
_available
_fields
()
609 # Fetch list of all threads of the monitored pid, as qemu
610 # starts a thread for each vcpu.
611 path
= os
.path
.join('/proc', str(self
._pid
), 'task')
612 groupids
= self
.walkdir(path
)[1]
614 groupids
= self
._get
_online
_cpus
()
616 # The constant is needed as a buffer for python libs, std
617 # streams and other files that the script opens.
618 newlim
= len(groupids
) * len(fields
) + 50
620 softlim_
, hardlim
= resource
.getrlimit(resource
.RLIMIT_NOFILE
)
623 # Now we need CAP_SYS_RESOURCE, to increase the hard limit.
624 resource
.setrlimit(resource
.RLIMIT_NOFILE
, (newlim
, newlim
))
626 # Raising the soft limit is sufficient.
627 resource
.setrlimit(resource
.RLIMIT_NOFILE
, (newlim
, hardlim
))
630 sys
.exit("NOFILE rlimit could not be raised to {0}".format(newlim
))
632 for groupid
in groupids
:
637 match
= re
.match(r
'(.*)\((.*)\)', name
)
639 tracepoint
, sub
= match
.groups()
640 tracefilter
= ('%s==%d\0' %
641 (self
.filters
[tracepoint
][0],
642 self
.filters
[tracepoint
][1][sub
]))
644 # From perf_event_open(2):
645 # pid > 0 and cpu == -1
646 # This measures the specified process/thread on any CPU.
648 # pid == -1 and cpu >= 0
649 # This measures all processes/threads on the specified CPU.
650 trace_cpu
= groupid
if self
._pid
== 0 else -1
651 trace_pid
= int(groupid
) if self
._pid
!= 0 else -1
653 group
.add_event(Event(name
=name
,
657 trace_point
=tracepoint
,
658 trace_filter
=tracefilter
))
660 self
.group_leaders
.append(group
)
667 def fields(self
, fields
):
668 """Enables/disables the (un)wanted events"""
669 self
._fields
= fields
670 for group
in self
.group_leaders
:
671 for index
, event
in enumerate(group
.events
):
672 if event
.name
in fields
:
676 # Do not disable the group leader.
677 # It would disable all of its events.
687 """Changes the monitored pid by setting new traces."""
689 # The garbage collector will get rid of all Event/Group
690 # objects and open files after removing the references.
691 self
.group_leaders
= []
693 self
.fields
= self
._fields
695 def read(self
, by_guest
=0):
696 """Returns 'event name: current value' for all enabled events."""
697 ret
= defaultdict(int)
698 for group
in self
.group_leaders
:
699 for name
, val
in group
.read().items():
700 if name
not in self
._fields
:
702 parent
= ARCH
.tracepoint_is_child(name
)
709 """Reset all field counters"""
710 for group
in self
.group_leaders
:
711 for event
in group
.events
:
715 class DebugfsProvider(Provider
):
716 """Provides data from the files that KVM creates in the kvm debugfs
718 def __init__(self
, pid
, fields_filter
, include_past
):
719 self
.update_fields(fields_filter
)
723 super(DebugfsProvider
, self
).__init
__(pid
)
727 def _get_available_fields(self
):
728 """"Returns a list of available fields.
730 The fields are all available KVM debugfs files
733 return self
.walkdir(PATH_DEBUGFS_KVM
)[2]
735 def update_fields(self
, fields_filter
):
736 """Refresh fields, applying fields_filter"""
737 self
._fields
= [field
for field
in self
._get
_available
_fields
()
738 if self
.is_field_wanted(fields_filter
, field
) or
739 ARCH
.debugfs_is_child(field
)]
746 def fields(self
, fields
):
747 self
._fields
= fields
758 vms
= self
.walkdir(PATH_DEBUGFS_KVM
)[1]
762 self
.paths
= filter(lambda x
: "{}-".format(pid
) in x
, vms
)
769 def read(self
, reset
=0, by_guest
=0):
770 """Returns a dict with format:'file name / field -> current value'.
774 1 reset field counts to 0
775 2 restore the original field counts
780 # If no debugfs filtering support is available, then don't read.
787 for entry
in os
.walk(PATH_DEBUGFS_KVM
):
791 for field
in self
._fields
:
792 value
= self
._read
_field
(field
, path
)
795 self
._baseline
[key
] = value
797 self
._baseline
[key
] = 0
798 if self
._baseline
.get(key
, -1) == -1:
799 self
._baseline
[key
] = value
800 parent
= ARCH
.debugfs_is_child(field
)
802 field
= field
+ ' ' + parent
805 field
= key
.split('-')[0] # set 'field' to 'pid'
806 increment
= value
- self
._baseline
.get(key
, 0)
808 results
[field
] += increment
810 results
[field
] = increment
814 def _read_field(self
, field
, path
):
815 """Returns the value of a single field from a specific VM."""
817 return int(open(os
.path
.join(PATH_DEBUGFS_KVM
,
825 """Reset field counters"""
830 """Reset field counters"""
835 EventStat
= namedtuple('EventStat', ['value', 'delta'])
839 """Manages the data providers and the data they provide.
841 It is used to set filters on the provider's data and collect all
845 def __init__(self
, options
):
846 self
.providers
= self
._get
_providers
(options
)
847 self
._pid
_filter
= options
.pid
848 self
._fields
_filter
= options
.fields
850 self
._child
_events
= False
852 def _get_providers(self
, options
):
853 """Returns a list of data providers depending on the passed options."""
857 providers
.append(DebugfsProvider(options
.pid
, options
.fields
,
858 options
.dbgfs_include_past
))
859 if options
.tracepoints
or not providers
:
860 providers
.append(TracepointProvider(options
.pid
, options
.fields
))
864 def _update_provider_filters(self
):
865 """Propagates fields filters to providers."""
866 # As we reset the counters when updating the fields we can
867 # also clear the cache of old values.
869 for provider
in self
.providers
:
870 provider
.update_fields(self
._fields
_filter
)
874 for provider
in self
.providers
:
878 def fields_filter(self
):
879 return self
._fields
_filter
881 @fields_filter.setter
882 def fields_filter(self
, fields_filter
):
883 if fields_filter
!= self
._fields
_filter
:
884 self
._fields
_filter
= fields_filter
885 self
._update
_provider
_filters
()
888 def pid_filter(self
):
889 return self
._pid
_filter
892 def pid_filter(self
, pid
):
893 if pid
!= self
._pid
_filter
:
894 self
._pid
_filter
= pid
896 for provider
in self
.providers
:
897 provider
.pid
= self
._pid
_filter
900 def child_events(self
):
901 return self
._child
_events
904 def child_events(self
, val
):
905 self
._child
_events
= val
906 for provider
in self
.providers
:
907 provider
.child_events
= val
909 def get(self
, by_guest
=0):
910 """Returns a dict with field -> (value, delta to last value) of all
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.
919 for provider
in self
.providers
:
920 new
= provider
.read(by_guest
=by_guest
)
922 oldval
= self
.values
.get(key
, EventStat(0, 0)).value
923 newval
= new
.get(key
, 0)
924 newdelta
= newval
- oldval
925 self
.values
[key
] = EventStat(newval
, newdelta
)
928 def toggle_display_guests(self
, to_pid
):
929 """Toggle between collection of stats by individual event and by
932 Events reported by DebugfsProvider change when switching to/from
933 reading by guest values. Hence we have to remove the excess event
934 names from self.values.
937 if any(isinstance(ins
, TracepointProvider
) for ins
in self
.providers
):
940 for provider
in self
.providers
:
941 if isinstance(provider
, DebugfsProvider
):
942 for key
in provider
.fields
:
943 if key
in self
.values
.keys():
946 oldvals
= self
.values
.copy()
950 # Update oldval (see get())
956 MAX_GUEST_NAME_LEN
= 48
962 """Instruments curses to draw a nice text ui."""
963 def __init__(self
, stats
):
966 self
._delay
_initial
= 0.25
967 self
._delay
_regular
= DELAY_DEFAULT
968 self
._sorting
= SORT_DEFAULT
969 self
._display
_guests
= 0
972 """Initialises curses for later use. Based on curses.wrapper
973 implementation from the Python standard library."""
974 self
.screen
= curses
.initscr()
978 # The try/catch works around a minor bit of
979 # over-conscientiousness in the curses module, the error
980 # return from C start_color() is ignorable.
986 # Hide cursor in extra statement as some monochrome terminals
987 # might support hiding but not colors.
993 curses
.use_default_colors()
996 def __exit__(self
, *exception
):
997 """Resets the terminal to its normal state. Based on curses.wrapper
998 implementation from the Python standard library."""
1000 self
.screen
.keypad(0)
1006 def get_all_gnames():
1007 """Returns a list of (pid, gname) tuples of all running guests"""
1010 child
= subprocess
.Popen(['ps', '-A', '--format', 'pid,args'],
1011 stdout
=subprocess
.PIPE
)
1014 for line
in child
.stdout
:
1015 line
= line
.decode(ENCODING
).lstrip().split(' ', 1)
1016 # perform a sanity check before calling the more expensive
1017 # function to possibly extract the guest name
1018 if ' -name ' in line
[1]:
1019 res
.append((line
[0], Tui
.get_gname_from_pid(line
[0])))
1020 child
.stdout
.close()
1024 def _print_all_gnames(self
, row
):
1025 """Print a list of all running guests along with their pids."""
1026 self
.screen
.addstr(row
, 2, '%8s %-60s' %
1027 ('Pid', 'Guest Name (fuzzy list, might be '
1032 for line
in self
.get_all_gnames():
1033 self
.screen
.addstr(row
, 2, '%8s %-60s' % (line
[0], line
[1]))
1035 if row
>= self
.screen
.getmaxyx()[0]:
1038 self
.screen
.addstr(row
+ 1, 2, 'Not available')
1041 def get_pid_from_gname(gname
):
1042 """Fuzzy function to convert guest name to QEMU process pid.
1044 Returns a list of potential pids, can be empty if no match found.
1045 Throws an exception on processing errors.
1049 for line
in Tui
.get_all_gnames():
1050 if gname
== line
[1]:
1051 pids
.append(int(line
[0]))
1056 def get_gname_from_pid(pid
):
1057 """Returns the guest name for a QEMU process pid.
1059 Extracts the guest name from the QEMU comma line by processing the
1060 '-name' option. Will also handle names specified out of sequence.
1065 line
= open('/proc/{}/cmdline'
1066 .format(pid
), 'r').read().split('\0')
1067 parms
= line
[line
.index('-name') + 1].split(',')
1069 # commas are escaped (i.e. ',,'), hence e.g. 'foo,bar' results
1070 # in # ['foo', '', 'bar'], which we revert here
1071 idx
= parms
.index('')
1072 parms
[idx
- 1] += ',' + parms
[idx
+ 1]
1073 del parms
[idx
:idx
+2]
1074 # the '-name' switch allows for two ways to specify the guest name,
1075 # where the plain name overrides the name specified via 'guest='
1080 if arg
[:6] == 'guest=':
1082 except (ValueError, IOError, IndexError):
1087 def _update_pid(self
, pid
):
1088 """Propagates pid selection to stats object."""
1089 self
.screen
.addstr(4, 1, 'Updating pid filter...')
1090 self
.screen
.refresh()
1091 self
.stats
.pid_filter
= pid
1093 def _refresh_header(self
, pid
=None):
1094 """Refreshes the header."""
1096 pid
= self
.stats
.pid_filter
1098 gname
= self
.get_gname_from_pid(pid
)
1100 gname
= ('({})'.format(gname
[:MAX_GUEST_NAME_LEN
] + '...'
1101 if len(gname
) > MAX_GUEST_NAME_LEN
1104 self
.screen
.addstr(0, 0, 'kvm statistics - pid {0} {1}'
1105 .format(pid
, gname
), curses
.A_BOLD
)
1107 self
.screen
.addstr(0, 0, 'kvm statistics - summary', curses
.A_BOLD
)
1108 if self
.stats
.fields_filter
:
1109 regex
= self
.stats
.fields_filter
1110 if len(regex
) > MAX_REGEX_LEN
:
1111 regex
= regex
[:MAX_REGEX_LEN
] + '...'
1112 self
.screen
.addstr(1, 17, 'regex filter: {0}'.format(regex
))
1113 if self
._display
_guests
:
1114 col_name
= 'Guest Name'
1117 self
.screen
.addstr(2, 1, '%-40s %10s%7s %8s' %
1118 (col_name
, 'Total', '%Total', 'CurAvg/s'),
1120 self
.screen
.addstr(4, 1, 'Collecting data...')
1121 self
.screen
.refresh()
1123 def _refresh_body(self
, sleeptime
):
1124 def is_child_field(field
):
1125 return field
.find('(') != -1
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
))
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
)
1141 def sortkey((_k
, v
)):
1142 # sort by overall value
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
,
1151 if values
== (0, 0):
1153 if key
.find(' ') != -1:
1154 if not self
.stats
.child_events
:
1156 childs
.insert(0, (key
, values
))
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
)
1167 self
.screen
.move(row
, 0)
1168 self
.screen
.clrtobot()
1169 stats
= self
.stats
.get(self
._display
_guests
)
1172 for key
, values
in stats
.items():
1173 if self
._display
_guests
:
1174 if self
.get_gname_from_pid(key
):
1175 total
+= values
.value
1177 if not key
.find(' ') != -1:
1178 total
+= values
.value
1180 ctotal
+= values
.value
1182 # we don't have any fields, or all non-child events are filtered
1188 for key
, values
in get_sorted_events(self
, stats
):
1189 if row
>= self
.screen
.getmaxyx()[0] - 1 or values
== (0, 0):
1191 if self
._display
_guests
:
1192 key
= self
.get_gname_from_pid(key
)
1195 cur
= int(round(values
.delta
/ sleeptime
)) if values
.delta
else ''
1198 tcur
+= values
.delta
1199 ptotal
= values
.value
1203 self
.screen
.addstr(row
, 1, '%-40s %10d%7.1f %8s' % (key
,
1205 values
.value
* 100 / float(ltotal
), cur
))
1208 self
.screen
.addstr(4, 1, 'No matching events reported yet')
1210 tavg
= int(round(tcur
/ sleeptime
)) if tcur
> 0 else ''
1211 self
.screen
.addstr(row
, 1, '%-40s %10d %8s' %
1212 ('Total', total
, tavg
), curses
.A_BOLD
)
1213 self
.screen
.refresh()
1215 def _show_msg(self
, text
):
1216 """Display message centered text and exit on key press"""
1217 hint
= 'Press any key to continue'
1220 (x
, term_width
) = self
.screen
.getmaxyx()
1223 start
= (term_width
- len(line
)) / 2
1224 self
.screen
.addstr(row
, start
, line
)
1226 self
.screen
.addstr(row
+ 1, (term_width
- len(hint
)) / 2, hint
,
1228 self
.screen
.getkey()
1230 def _show_help_interactive(self
):
1231 """Display help with list of interactive commands"""
1232 msg
= (' b toggle events by guests (debugfs only, honors'
1235 ' f filter by regular expression',
1236 ' g filter by guest name/PID',
1237 ' h display interactive commands reference',
1238 ' o toggle sorting order (Total vs CurAvg/s)',
1239 ' p filter by guest name/PID',
1242 ' s set update interval',
1243 ' x toggle reporting of stats for individual child trace'
1245 'Any other key refreshes statistics immediately')
1248 self
.screen
.addstr(0, 0, "Interactive commands reference",
1250 self
.screen
.addstr(2, 0, "Press any key to exit", curses
.A_STANDOUT
)
1253 self
.screen
.addstr(row
, 0, line
)
1255 self
.screen
.getkey()
1256 self
._refresh
_header
()
1258 def _show_filter_selection(self
):
1259 """Draws filter selection mask.
1261 Asks for a valid regex and sets the fields filter accordingly.
1267 self
.screen
.addstr(0, 0,
1268 "Show statistics for events matching a regex.",
1270 self
.screen
.addstr(2, 0,
1271 "Current regex: {0}"
1272 .format(self
.stats
.fields_filter
))
1273 self
.screen
.addstr(5, 0, msg
)
1274 self
.screen
.addstr(3, 0, "New regex: ")
1276 regex
= self
.screen
.getstr().decode(ENCODING
)
1279 self
.stats
.fields_filter
= ''
1280 self
._refresh
_header
()
1284 self
.stats
.fields_filter
= regex
1285 self
._refresh
_header
()
1288 msg
= '"' + regex
+ '": Not a valid regular expression'
1291 def _show_set_update_interval(self
):
1292 """Draws update interval selection mask."""
1296 self
.screen
.addstr(0, 0, 'Set update interval (defaults to %fs).' %
1297 DELAY_DEFAULT
, curses
.A_BOLD
)
1298 self
.screen
.addstr(4, 0, msg
)
1299 self
.screen
.addstr(2, 0, 'Change delay from %.1fs to ' %
1300 self
._delay
_regular
)
1302 val
= self
.screen
.getstr().decode(ENCODING
)
1309 msg
= '"' + str(val
) + '": Value must be >=0.1'
1312 msg
= '"' + str(val
) + '": Value must be <=25.5'
1315 delay
= DELAY_DEFAULT
1316 self
._delay
_regular
= delay
1320 msg
= '"' + str(val
) + '": Invalid value'
1321 self
._refresh
_header
()
1323 def _show_vm_selection_by_guest(self
):
1324 """Draws guest selection mask.
1326 Asks for a guest name or pid until a valid guest name or '' is entered.
1332 self
.screen
.addstr(0, 0,
1333 'Show statistics for specific guest or pid.',
1335 self
.screen
.addstr(1, 0,
1336 'This might limit the shown data to the trace '
1338 self
.screen
.addstr(5, 0, msg
)
1339 self
._print
_all
_gnames
(7)
1342 self
.screen
.addstr(3, 0, "Guest or pid [ENTER exits]: ")
1343 guest
= self
.screen
.getstr().decode(ENCODING
)
1347 if not guest
or guest
== '0':
1350 if not os
.path
.isdir(os
.path
.join('/proc/', guest
)):
1351 msg
= '"' + guest
+ '": Not a running process'
1357 pids
= self
.get_pid_from_gname(guest
)
1359 msg
= '"' + guest
+ '": Internal error while searching, ' \
1360 'use pid filter instead'
1363 msg
= '"' + guest
+ '": Not an active guest'
1366 msg
= '"' + guest
+ '": Multiple matches found, use pid ' \
1372 self
._refresh
_header
(pid
)
1373 self
._update
_pid
(pid
)
1375 def show_stats(self
):
1376 """Refreshes the screen and processes user input."""
1377 sleeptime
= self
._delay
_initial
1378 self
._refresh
_header
()
1379 start
= 0.0 # result based on init value never appears on screen
1381 self
._refresh
_body
(time
.time() - start
)
1382 curses
.halfdelay(int(sleeptime
* 10))
1384 sleeptime
= self
._delay
_regular
1386 char
= self
.screen
.getkey()
1388 self
._display
_guests
= not self
._display
_guests
1389 if self
.stats
.toggle_display_guests(self
._display
_guests
):
1390 self
._show
_msg
(['Command not available with '
1391 'tracepoints enabled', 'Restart with '
1392 'debugfs only (see option \'-d\') and '
1394 self
._display
_guests
= not self
._display
_guests
1395 self
._refresh
_header
()
1397 self
.stats
.fields_filter
= ''
1398 self
._refresh
_header
(0)
1402 self
._show
_filter
_selection
()
1404 sleeptime
= self
._delay
_initial
1405 if char
== 'g' or char
== 'p':
1406 self
._show
_vm
_selection
_by
_guest
()
1407 sleeptime
= self
._delay
_initial
1409 self
._show
_help
_interactive
()
1411 self
._sorting
= not self
._sorting
1418 self
._show
_set
_update
_interval
()
1420 sleeptime
= self
._delay
_initial
1422 self
.stats
.child_events
= not self
.stats
.child_events
1423 except KeyboardInterrupt:
1425 except curses
.error
:
1430 """Prints statistics in a key, value format."""
1435 for key
, values
in sorted(s
.items()):
1436 print('%-42s%10d%10d' % (key
.split(' ')[0], values
.value
,
1438 except KeyboardInterrupt:
1443 """Prints statistics as reiterating key block, multiple value blocks."""
1444 keys
= sorted(stats
.get().keys())
1448 print(key
.split(' ')[0], end
=' ')
1454 print(' %9d' % s
[key
].delta
, end
=' ')
1461 if line
% banner_repeat
== 0:
1465 except KeyboardInterrupt:
1470 """Returns processed program arguments."""
1471 description_text
= """
1472 This script displays various statistics about VMs running under KVM.
1473 The statistics are gathered from the KVM debugfs entries and / or the
1474 currently available perf traces.
1476 The monitoring takes additional cpu cycles and might affect the VM's
1484 - /proc/sys/kernel/perf_event_paranoid < 1 if user has no
1485 CAP_SYS_ADMIN and perf events are used.
1486 - CAP_SYS_RESOURCE if the hard limit is not high enough to allow
1487 the large number of files that are possibly opened.
1489 Interactive Commands:
1490 b toggle events by guests (debugfs only, honors filters)
1492 f filter by regular expression
1493 g filter by guest name
1494 h display interactive commands reference
1495 o toggle sorting order (Total vs CurAvg/s)
1499 s set update interval
1500 x toggle reporting of stats for individual child trace events
1501 Press any other key to refresh statistics immediately.
1502 """ % (PATH_DEBUGFS_KVM
, PATH_DEBUGFS_TRACING
)
1504 class PlainHelpFormatter(optparse
.IndentedHelpFormatter
):
1505 def format_description(self
, description
):
1507 return description
+ "\n"
1511 def cb_guest_to_pid(option
, opt
, val
, parser
):
1513 pids
= Tui
.get_pid_from_gname(val
)
1515 sys
.exit('Error while searching for guest "{}". Use "-p" to '
1516 'specify a pid instead?'.format(val
))
1518 sys
.exit('Error: No guest by the name "{}" found'.format(val
))
1520 sys
.exit('Error: Multiple processes found (pids: {}). Use "-p" '
1521 'to specify the desired pid'.format(" ".join(pids
)))
1522 parser
.values
.pid
= pids
[0]
1524 optparser
= optparse
.OptionParser(description
=description_text
,
1525 formatter
=PlainHelpFormatter())
1526 optparser
.add_option('-1', '--once', '--batch',
1527 action
='store_true',
1530 help='run in batch mode for one second',
1532 optparser
.add_option('-i', '--debugfs-include-past',
1533 action
='store_true',
1535 dest
='dbgfs_include_past',
1536 help='include all available data on past events for '
1539 optparser
.add_option('-l', '--log',
1540 action
='store_true',
1543 help='run in logging mode (like vmstat)',
1545 optparser
.add_option('-t', '--tracepoints',
1546 action
='store_true',
1549 help='retrieve statistics from tracepoints',
1551 optparser
.add_option('-d', '--debugfs',
1552 action
='store_true',
1555 help='retrieve statistics from debugfs',
1557 optparser
.add_option('-f', '--fields',
1561 help='''fields to display (regex)
1562 "-f help" for a list of available events''',
1564 optparser
.add_option('-p', '--pid',
1569 help='restrict statistics to pid',
1571 optparser
.add_option('-g', '--guest',
1576 help='restrict statistics to guest by name',
1577 callback
=cb_guest_to_pid
,
1579 options
, unkn
= optparser
.parse_args(sys
.argv
)
1581 sys
.exit('Error: Extra argument(s): ' + ' '.join(unkn
[1:]))
1583 # verify that we were passed a valid regex up front
1584 re
.compile(options
.fields
)
1586 sys
.exit('Error: "' + options
.fields
+ '" is not a valid regular '
1592 def check_access(options
):
1593 """Exits if the current user can't access all needed directories."""
1594 if not os
.path
.exists(PATH_DEBUGFS_TRACING
) and (options
.tracepoints
or
1595 not options
.debugfs
):
1596 sys
.stderr
.write("Please enable CONFIG_TRACING in your kernel "
1597 "when using the option -t (default).\n"
1598 "If it is enabled, make {0} readable by the "
1600 .format(PATH_DEBUGFS_TRACING
))
1601 if options
.tracepoints
:
1604 sys
.stderr
.write("Falling back to debugfs statistics!\n")
1605 options
.debugfs
= True
1611 def assign_globals():
1612 global PATH_DEBUGFS_KVM
1613 global PATH_DEBUGFS_TRACING
1616 for line
in file('/proc/mounts'):
1617 if line
.split(' ')[0] == 'debugfs':
1618 debugfs
= line
.split(' ')[1]
1621 sys
.stderr
.write("Please make sure that CONFIG_DEBUG_FS is enabled in "
1622 "your kernel, mounted and\nreadable by the current "
1624 "('mount -t debugfs debugfs /sys/kernel/debug')\n")
1627 PATH_DEBUGFS_KVM
= os
.path
.join(debugfs
, 'kvm')
1628 PATH_DEBUGFS_TRACING
= os
.path
.join(debugfs
, 'tracing')
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")
1638 options
= get_options()
1639 options
= check_access(options
)
1641 if (options
.pid
> 0 and
1642 not os
.path
.isdir(os
.path
.join('/proc/',
1643 str(options
.pid
)))):
1644 sys
.stderr
.write('Did you use a (unsupported) tid instead of a pid?\n')
1645 sys
.exit('Specified pid does not exist.')
1647 stats
= Stats(options
)
1649 if options
.fields
== 'help':
1650 stats
.fields_filter
= None
1652 for key
in stats
.get().keys():
1653 event_list
.append(key
.split('(', 1)[0])
1654 sys
.stdout
.write(' ' + '\n '.join(sorted(set(event_list
))) + '\n')
1659 elif not options
.once
:
1660 with
Tui(stats
) as tui
:
1665 if __name__
== "__main__":