]>
git.ipfire.org Git - thirdparty/kernel/stable.git/blob - tools/kvm/kvm_stat/kvm_stat
bc508dae286c8bfb75d0c2d9165b073d1f0ed178
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
)]
579 # add parents for child fields - otherwise we won't see any output!
580 for field
in self
._fields
:
581 parent
= ARCH
.tracepoint_is_child(field
)
582 if (parent
and parent
not in self
._fields
):
583 self
.fields
.append(parent
)
586 def _get_online_cpus():
587 """Returns a list of cpu id integers."""
588 def parse_int_list(list_string
):
589 """Returns an int list from a string of comma separated integers and
592 members
= list_string
.split(',')
594 for member
in members
:
595 if '-' not in member
:
596 integers
.append(int(member
))
598 int_range
= member
.split('-')
599 integers
.extend(range(int(int_range
[0]),
600 int(int_range
[1]) + 1))
604 with
open('/sys/devices/system/cpu/online') as cpu_list
:
605 cpu_string
= cpu_list
.readline()
606 return parse_int_list(cpu_string
)
608 def _setup_traces(self
):
609 """Creates all event and group objects needed to be able to retrieve
611 fields
= self
._get
_available
_fields
()
613 # Fetch list of all threads of the monitored pid, as qemu
614 # starts a thread for each vcpu.
615 path
= os
.path
.join('/proc', str(self
._pid
), 'task')
616 groupids
= self
.walkdir(path
)[1]
618 groupids
= self
._get
_online
_cpus
()
620 # The constant is needed as a buffer for python libs, std
621 # streams and other files that the script opens.
622 newlim
= len(groupids
) * len(fields
) + 50
624 softlim_
, hardlim
= resource
.getrlimit(resource
.RLIMIT_NOFILE
)
627 # Now we need CAP_SYS_RESOURCE, to increase the hard limit.
628 resource
.setrlimit(resource
.RLIMIT_NOFILE
, (newlim
, newlim
))
630 # Raising the soft limit is sufficient.
631 resource
.setrlimit(resource
.RLIMIT_NOFILE
, (newlim
, hardlim
))
634 sys
.exit("NOFILE rlimit could not be raised to {0}".format(newlim
))
636 for groupid
in groupids
:
641 match
= re
.match(r
'(.*)\((.*)\)', name
)
643 tracepoint
, sub
= match
.groups()
644 tracefilter
= ('%s==%d\0' %
645 (self
.filters
[tracepoint
][0],
646 self
.filters
[tracepoint
][1][sub
]))
648 # From perf_event_open(2):
649 # pid > 0 and cpu == -1
650 # This measures the specified process/thread on any CPU.
652 # pid == -1 and cpu >= 0
653 # This measures all processes/threads on the specified CPU.
654 trace_cpu
= groupid
if self
._pid
== 0 else -1
655 trace_pid
= int(groupid
) if self
._pid
!= 0 else -1
657 group
.add_event(Event(name
=name
,
661 trace_point
=tracepoint
,
662 trace_filter
=tracefilter
))
664 self
.group_leaders
.append(group
)
671 def fields(self
, fields
):
672 """Enables/disables the (un)wanted events"""
673 self
._fields
= fields
674 for group
in self
.group_leaders
:
675 for index
, event
in enumerate(group
.events
):
676 if event
.name
in fields
:
680 # Do not disable the group leader.
681 # It would disable all of its events.
691 """Changes the monitored pid by setting new traces."""
693 # The garbage collector will get rid of all Event/Group
694 # objects and open files after removing the references.
695 self
.group_leaders
= []
697 self
.fields
= self
._fields
699 def read(self
, by_guest
=0):
700 """Returns 'event name: current value' for all enabled events."""
701 ret
= defaultdict(int)
702 for group
in self
.group_leaders
:
703 for name
, val
in group
.read().items():
704 if name
not in self
._fields
:
706 parent
= ARCH
.tracepoint_is_child(name
)
713 """Reset all field counters"""
714 for group
in self
.group_leaders
:
715 for event
in group
.events
:
719 class DebugfsProvider(Provider
):
720 """Provides data from the files that KVM creates in the kvm debugfs
722 def __init__(self
, pid
, fields_filter
, include_past
):
723 self
.update_fields(fields_filter
)
727 super(DebugfsProvider
, self
).__init
__(pid
)
731 def _get_available_fields(self
):
732 """"Returns a list of available fields.
734 The fields are all available KVM debugfs files
737 return self
.walkdir(PATH_DEBUGFS_KVM
)[2]
739 def update_fields(self
, fields_filter
):
740 """Refresh fields, applying fields_filter"""
741 self
._fields
= [field
for field
in self
._get
_available
_fields
()
742 if self
.is_field_wanted(fields_filter
, field
)]
743 # add parents for child fields - otherwise we won't see any output!
744 for field
in self
._fields
:
745 parent
= ARCH
.debugfs_is_child(field
)
746 if (parent
and parent
not in self
._fields
):
747 self
.fields
.append(parent
)
754 def fields(self
, fields
):
755 self
._fields
= fields
766 vms
= self
.walkdir(PATH_DEBUGFS_KVM
)[1]
770 self
.paths
= list(filter(lambda x
: "{}-".format(pid
) in x
, vms
))
776 def _verify_paths(self
):
777 """Remove invalid paths"""
778 for path
in self
.paths
:
779 if not os
.path
.exists(os
.path
.join(PATH_DEBUGFS_KVM
, path
)):
780 self
.paths
.remove(path
)
783 def read(self
, reset
=0, by_guest
=0):
784 """Returns a dict with format:'file name / field -> current value'.
788 1 reset field counts to 0
789 2 restore the original field counts
794 # If no debugfs filtering support is available, then don't read.
802 for entry
in os
.walk(PATH_DEBUGFS_KVM
):
806 for field
in self
._fields
:
807 value
= self
._read
_field
(field
, path
)
810 self
._baseline
[key
] = value
812 self
._baseline
[key
] = 0
813 if self
._baseline
.get(key
, -1) == -1:
814 self
._baseline
[key
] = value
815 parent
= ARCH
.debugfs_is_child(field
)
817 field
= field
+ ' ' + parent
820 field
= key
.split('-')[0] # set 'field' to 'pid'
821 increment
= value
- self
._baseline
.get(key
, 0)
823 results
[field
] += increment
825 results
[field
] = increment
829 def _read_field(self
, field
, path
):
830 """Returns the value of a single field from a specific VM."""
832 return int(open(os
.path
.join(PATH_DEBUGFS_KVM
,
840 """Reset field counters"""
845 """Reset field counters"""
850 EventStat
= namedtuple('EventStat', ['value', 'delta'])
854 """Manages the data providers and the data they provide.
856 It is used to set filters on the provider's data and collect all
860 def __init__(self
, options
):
861 self
.providers
= self
._get
_providers
(options
)
862 self
._pid
_filter
= options
.pid
863 self
._fields
_filter
= options
.fields
865 self
._child
_events
= False
867 def _get_providers(self
, options
):
868 """Returns a list of data providers depending on the passed options."""
872 providers
.append(DebugfsProvider(options
.pid
, options
.fields
,
873 options
.dbgfs_include_past
))
874 if options
.tracepoints
or not providers
:
875 providers
.append(TracepointProvider(options
.pid
, options
.fields
))
879 def _update_provider_filters(self
):
880 """Propagates fields filters to providers."""
881 # As we reset the counters when updating the fields we can
882 # also clear the cache of old values.
884 for provider
in self
.providers
:
885 provider
.update_fields(self
._fields
_filter
)
889 for provider
in self
.providers
:
893 def fields_filter(self
):
894 return self
._fields
_filter
896 @fields_filter.setter
897 def fields_filter(self
, fields_filter
):
898 if fields_filter
!= self
._fields
_filter
:
899 self
._fields
_filter
= fields_filter
900 self
._update
_provider
_filters
()
903 def pid_filter(self
):
904 return self
._pid
_filter
907 def pid_filter(self
, pid
):
908 if pid
!= self
._pid
_filter
:
909 self
._pid
_filter
= pid
911 for provider
in self
.providers
:
912 provider
.pid
= self
._pid
_filter
915 def child_events(self
):
916 return self
._child
_events
919 def child_events(self
, val
):
920 self
._child
_events
= val
921 for provider
in self
.providers
:
922 provider
.child_events
= val
924 def get(self
, by_guest
=0):
925 """Returns a dict with field -> (value, delta to last value) of all
928 * plain: 'key' is event name
929 * child-parent: 'key' is in format '<child> <parent>'
930 * pid: 'key' is the pid of the guest, and the record contains the
931 aggregated event data
932 These formats are generated by the providers, and handled in class TUI.
934 for provider
in self
.providers
:
935 new
= provider
.read(by_guest
=by_guest
)
937 oldval
= self
.values
.get(key
, EventStat(0, 0)).value
938 newval
= new
.get(key
, 0)
939 newdelta
= newval
- oldval
940 self
.values
[key
] = EventStat(newval
, newdelta
)
943 def toggle_display_guests(self
, to_pid
):
944 """Toggle between collection of stats by individual event and by
947 Events reported by DebugfsProvider change when switching to/from
948 reading by guest values. Hence we have to remove the excess event
949 names from self.values.
952 if any(isinstance(ins
, TracepointProvider
) for ins
in self
.providers
):
955 for provider
in self
.providers
:
956 if isinstance(provider
, DebugfsProvider
):
957 for key
in provider
.fields
:
958 if key
in self
.values
.keys():
961 oldvals
= self
.values
.copy()
965 # Update oldval (see get())
971 MAX_GUEST_NAME_LEN
= 48
977 """Instruments curses to draw a nice text ui."""
978 def __init__(self
, stats
):
981 self
._delay
_initial
= 0.25
982 self
._delay
_regular
= DELAY_DEFAULT
983 self
._sorting
= SORT_DEFAULT
984 self
._display
_guests
= 0
987 """Initialises curses for later use. Based on curses.wrapper
988 implementation from the Python standard library."""
989 self
.screen
= curses
.initscr()
993 # The try/catch works around a minor bit of
994 # over-conscientiousness in the curses module, the error
995 # return from C start_color() is ignorable.
1001 # Hide cursor in extra statement as some monochrome terminals
1002 # might support hiding but not colors.
1005 except curses
.error
:
1008 curses
.use_default_colors()
1011 def __exit__(self
, *exception
):
1012 """Resets the terminal to its normal state. Based on curses.wrapper
1013 implementation from the Python standard library."""
1015 self
.screen
.keypad(0)
1021 def get_all_gnames():
1022 """Returns a list of (pid, gname) tuples of all running guests"""
1025 child
= subprocess
.Popen(['ps', '-A', '--format', 'pid,args'],
1026 stdout
=subprocess
.PIPE
)
1029 for line
in child
.stdout
:
1030 line
= line
.decode(ENCODING
).lstrip().split(' ', 1)
1031 # perform a sanity check before calling the more expensive
1032 # function to possibly extract the guest name
1033 if ' -name ' in line
[1]:
1034 res
.append((line
[0], Tui
.get_gname_from_pid(line
[0])))
1035 child
.stdout
.close()
1039 def _print_all_gnames(self
, row
):
1040 """Print a list of all running guests along with their pids."""
1041 self
.screen
.addstr(row
, 2, '%8s %-60s' %
1042 ('Pid', 'Guest Name (fuzzy list, might be '
1047 for line
in self
.get_all_gnames():
1048 self
.screen
.addstr(row
, 2, '%8s %-60s' % (line
[0], line
[1]))
1050 if row
>= self
.screen
.getmaxyx()[0]:
1053 self
.screen
.addstr(row
+ 1, 2, 'Not available')
1056 def get_pid_from_gname(gname
):
1057 """Fuzzy function to convert guest name to QEMU process pid.
1059 Returns a list of potential pids, can be empty if no match found.
1060 Throws an exception on processing errors.
1064 for line
in Tui
.get_all_gnames():
1065 if gname
== line
[1]:
1066 pids
.append(int(line
[0]))
1071 def get_gname_from_pid(pid
):
1072 """Returns the guest name for a QEMU process pid.
1074 Extracts the guest name from the QEMU comma line by processing the
1075 '-name' option. Will also handle names specified out of sequence.
1080 line
= open('/proc/{}/cmdline'
1081 .format(pid
), 'r').read().split('\0')
1082 parms
= line
[line
.index('-name') + 1].split(',')
1084 # commas are escaped (i.e. ',,'), hence e.g. 'foo,bar' results
1085 # in # ['foo', '', 'bar'], which we revert here
1086 idx
= parms
.index('')
1087 parms
[idx
- 1] += ',' + parms
[idx
+ 1]
1088 del parms
[idx
:idx
+2]
1089 # the '-name' switch allows for two ways to specify the guest name,
1090 # where the plain name overrides the name specified via 'guest='
1095 if arg
[:6] == 'guest=':
1097 except (ValueError, IOError, IndexError):
1102 def _update_pid(self
, pid
):
1103 """Propagates pid selection to stats object."""
1104 self
.screen
.addstr(4, 1, 'Updating pid filter...')
1105 self
.screen
.refresh()
1106 self
.stats
.pid_filter
= pid
1108 def _refresh_header(self
, pid
=None):
1109 """Refreshes the header."""
1111 pid
= self
.stats
.pid_filter
1113 gname
= self
.get_gname_from_pid(pid
)
1116 gname
= ('({})'.format(gname
[:MAX_GUEST_NAME_LEN
] + '...'
1117 if len(gname
) > MAX_GUEST_NAME_LEN
1120 self
._headline
= 'kvm statistics - pid {0} {1}'.format(pid
, gname
)
1122 self
._headline
= 'kvm statistics - summary'
1123 self
.screen
.addstr(0, 0, self
._headline
, curses
.A_BOLD
)
1124 if self
.stats
.fields_filter
:
1125 regex
= self
.stats
.fields_filter
1126 if len(regex
) > MAX_REGEX_LEN
:
1127 regex
= regex
[:MAX_REGEX_LEN
] + '...'
1128 self
.screen
.addstr(1, 17, 'regex filter: {0}'.format(regex
))
1129 if self
._display
_guests
:
1130 col_name
= 'Guest Name'
1133 self
.screen
.addstr(2, 1, '%-40s %10s%7s %8s' %
1134 (col_name
, 'Total', '%Total', 'CurAvg/s'),
1136 self
.screen
.addstr(4, 1, 'Collecting data...')
1137 self
.screen
.refresh()
1139 def _refresh_body(self
, sleeptime
):
1140 def insert_child(sorted_items
, child
, values
, parent
):
1141 num
= len(sorted_items
)
1142 for i
in range(0, num
):
1143 # only add child if parent is present
1144 if parent
.startswith(sorted_items
[i
][0]):
1145 sorted_items
.insert(i
+ 1, (' ' + child
, values
))
1147 def get_sorted_events(self
, stats
):
1148 """ separate parent and child events """
1149 if self
._sorting
== SORT_DEFAULT
:
1151 # sort by (delta value, overall value)
1153 return (v
.delta
, v
.value
)
1156 # sort by overall value
1162 # we can't rule out child events to appear prior to parents even
1163 # when sorted - separate out all children first, and add in later
1164 for key
, values
in sorted(stats
.items(), key
=sortkey
,
1166 if values
== (0, 0):
1168 if key
.find(' ') != -1:
1169 if not self
.stats
.child_events
:
1171 childs
.insert(0, (key
, values
))
1173 sorted_items
.append((key
, values
))
1174 if self
.stats
.child_events
:
1175 for key
, values
in childs
:
1176 (child
, parent
) = key
.split(' ')
1177 insert_child(sorted_items
, child
, values
, parent
)
1181 if not self
._is
_running
_guest
(self
.stats
.pid_filter
):
1183 try: # ...to identify the guest by name in case it's back
1184 pids
= self
.get_pid_from_gname(self
._gname
)
1186 self
._refresh
_header
(pids
[0])
1187 self
._update
_pid
(pids
[0])
1191 self
._display
_guest
_dead
()
1192 # leave final data on screen
1195 self
.screen
.move(row
, 0)
1196 self
.screen
.clrtobot()
1197 stats
= self
.stats
.get(self
._display
_guests
)
1200 for key
, values
in stats
.items():
1201 if self
._display
_guests
:
1202 if self
.get_gname_from_pid(key
):
1203 total
+= values
.value
1205 if not key
.find(' ') != -1:
1206 total
+= values
.value
1208 ctotal
+= values
.value
1210 # we don't have any fields, or all non-child events are filtered
1216 guest_removed
= False
1217 for key
, values
in get_sorted_events(self
, stats
):
1218 if row
>= self
.screen
.getmaxyx()[0] - 1 or values
== (0, 0):
1220 if self
._display
_guests
:
1221 key
= self
.get_gname_from_pid(key
)
1224 cur
= int(round(values
.delta
/ sleeptime
)) if values
.delta
else 0
1226 guest_removed
= True
1230 tcur
+= values
.delta
1231 ptotal
= values
.value
1235 self
.screen
.addstr(row
, 1, '%-40s %10d%7.1f %8s' % (key
,
1237 values
.value
* 100 / float(ltotal
), cur
))
1241 self
.screen
.addstr(4, 1, 'Guest removed, updating...')
1243 self
.screen
.addstr(4, 1, 'No matching events reported yet')
1245 tavg
= int(round(tcur
/ sleeptime
)) if tcur
> 0 else ''
1246 self
.screen
.addstr(row
, 1, '%-40s %10d %8s' %
1247 ('Total', total
, tavg
), curses
.A_BOLD
)
1248 self
.screen
.refresh()
1250 def _display_guest_dead(self
):
1251 marker
= ' Guest is DEAD '
1252 y
= min(len(self
._headline
), 80 - len(marker
))
1253 self
.screen
.addstr(0, y
, marker
, curses
.A_BLINK | curses
.A_STANDOUT
)
1255 def _show_msg(self
, text
):
1256 """Display message centered text and exit on key press"""
1257 hint
= 'Press any key to continue'
1260 (x
, term_width
) = self
.screen
.getmaxyx()
1263 start
= (term_width
- len(line
)) // 2
1264 self
.screen
.addstr(row
, start
, line
)
1266 self
.screen
.addstr(row
+ 1, (term_width
- len(hint
)) // 2, hint
,
1268 self
.screen
.getkey()
1270 def _show_help_interactive(self
):
1271 """Display help with list of interactive commands"""
1272 msg
= (' b toggle events by guests (debugfs only, honors'
1275 ' f filter by regular expression',
1276 ' g filter by guest name/PID',
1277 ' h display interactive commands reference',
1278 ' o toggle sorting order (Total vs CurAvg/s)',
1279 ' p filter by guest name/PID',
1282 ' s set update interval',
1283 ' x toggle reporting of stats for individual child trace'
1285 'Any other key refreshes statistics immediately')
1288 self
.screen
.addstr(0, 0, "Interactive commands reference",
1290 self
.screen
.addstr(2, 0, "Press any key to exit", curses
.A_STANDOUT
)
1293 self
.screen
.addstr(row
, 0, line
)
1295 self
.screen
.getkey()
1296 self
._refresh
_header
()
1298 def _show_filter_selection(self
):
1299 """Draws filter selection mask.
1301 Asks for a valid regex and sets the fields filter accordingly.
1307 self
.screen
.addstr(0, 0,
1308 "Show statistics for events matching a regex.",
1310 self
.screen
.addstr(2, 0,
1311 "Current regex: {0}"
1312 .format(self
.stats
.fields_filter
))
1313 self
.screen
.addstr(5, 0, msg
)
1314 self
.screen
.addstr(3, 0, "New regex: ")
1316 regex
= self
.screen
.getstr().decode(ENCODING
)
1319 self
.stats
.fields_filter
= ''
1320 self
._refresh
_header
()
1324 self
.stats
.fields_filter
= regex
1325 self
._refresh
_header
()
1328 msg
= '"' + regex
+ '": Not a valid regular expression'
1331 def _show_set_update_interval(self
):
1332 """Draws update interval selection mask."""
1336 self
.screen
.addstr(0, 0, 'Set update interval (defaults to %.1fs).' %
1337 DELAY_DEFAULT
, curses
.A_BOLD
)
1338 self
.screen
.addstr(4, 0, msg
)
1339 self
.screen
.addstr(2, 0, 'Change delay from %.1fs to ' %
1340 self
._delay
_regular
)
1342 val
= self
.screen
.getstr().decode(ENCODING
)
1349 msg
= '"' + str(val
) + '": Value must be >=0.1'
1352 msg
= '"' + str(val
) + '": Value must be <=25.5'
1355 delay
= DELAY_DEFAULT
1356 self
._delay
_regular
= delay
1360 msg
= '"' + str(val
) + '": Invalid value'
1361 self
._refresh
_header
()
1363 def _is_running_guest(self
, pid
):
1364 """Check if pid is still a running process."""
1367 return os
.path
.isdir(os
.path
.join('/proc/', str(pid
)))
1369 def _show_vm_selection_by_guest(self
):
1370 """Draws guest selection mask.
1372 Asks for a guest name or pid until a valid guest name or '' is entered.
1378 self
.screen
.addstr(0, 0,
1379 'Show statistics for specific guest or pid.',
1381 self
.screen
.addstr(1, 0,
1382 'This might limit the shown data to the trace '
1384 self
.screen
.addstr(5, 0, msg
)
1385 self
._print
_all
_gnames
(7)
1388 self
.screen
.addstr(3, 0, "Guest or pid [ENTER exits]: ")
1389 guest
= self
.screen
.getstr().decode(ENCODING
)
1393 if not guest
or guest
== '0':
1396 if not self
._is
_running
_guest
(guest
):
1397 msg
= '"' + guest
+ '": Not a running process'
1403 pids
= self
.get_pid_from_gname(guest
)
1405 msg
= '"' + guest
+ '": Internal error while searching, ' \
1406 'use pid filter instead'
1409 msg
= '"' + guest
+ '": Not an active guest'
1412 msg
= '"' + guest
+ '": Multiple matches found, use pid ' \
1418 self
._refresh
_header
(pid
)
1419 self
._update
_pid
(pid
)
1421 def show_stats(self
):
1422 """Refreshes the screen and processes user input."""
1423 sleeptime
= self
._delay
_initial
1424 self
._refresh
_header
()
1425 start
= 0.0 # result based on init value never appears on screen
1427 self
._refresh
_body
(time
.time() - start
)
1428 curses
.halfdelay(int(sleeptime
* 10))
1430 sleeptime
= self
._delay
_regular
1432 char
= self
.screen
.getkey()
1434 self
._display
_guests
= not self
._display
_guests
1435 if self
.stats
.toggle_display_guests(self
._display
_guests
):
1436 self
._show
_msg
(['Command not available with '
1437 'tracepoints enabled', 'Restart with '
1438 'debugfs only (see option \'-d\') and '
1440 self
._display
_guests
= not self
._display
_guests
1441 self
._refresh
_header
()
1443 self
.stats
.fields_filter
= ''
1444 self
._refresh
_header
(0)
1448 self
._show
_filter
_selection
()
1450 sleeptime
= self
._delay
_initial
1451 if char
== 'g' or char
== 'p':
1452 self
._show
_vm
_selection
_by
_guest
()
1453 sleeptime
= self
._delay
_initial
1455 self
._show
_help
_interactive
()
1457 self
._sorting
= not self
._sorting
1464 self
._show
_set
_update
_interval
()
1466 sleeptime
= self
._delay
_initial
1468 self
.stats
.child_events
= not self
.stats
.child_events
1469 except KeyboardInterrupt:
1471 except curses
.error
:
1476 """Prints statistics in a key, value format."""
1481 for key
, values
in sorted(s
.items()):
1482 print('%-42s%10d%10d' % (key
.split(' ')[0], values
.value
,
1484 except KeyboardInterrupt:
1489 """Prints statistics as reiterating key block, multiple value blocks."""
1490 keys
= sorted(stats
.get().keys())
1494 print(key
.split(' ')[0], end
=' ')
1500 print(' %9d' % s
[key
].delta
, end
=' ')
1507 if line
% banner_repeat
== 0:
1511 except KeyboardInterrupt:
1516 """Returns processed program arguments."""
1517 description_text
= """
1518 This script displays various statistics about VMs running under KVM.
1519 The statistics are gathered from the KVM debugfs entries and / or the
1520 currently available perf traces.
1522 The monitoring takes additional cpu cycles and might affect the VM's
1530 - /proc/sys/kernel/perf_event_paranoid < 1 if user has no
1531 CAP_SYS_ADMIN and perf events are used.
1532 - CAP_SYS_RESOURCE if the hard limit is not high enough to allow
1533 the large number of files that are possibly opened.
1535 Interactive Commands:
1536 b toggle events by guests (debugfs only, honors filters)
1538 f filter by regular expression
1539 g filter by guest name
1540 h display interactive commands reference
1541 o toggle sorting order (Total vs CurAvg/s)
1545 s set update interval
1546 x toggle reporting of stats for individual child trace events
1547 Press any other key to refresh statistics immediately.
1548 """ % (PATH_DEBUGFS_KVM
, PATH_DEBUGFS_TRACING
)
1550 class PlainHelpFormatter(optparse
.IndentedHelpFormatter
):
1551 def format_description(self
, description
):
1553 return description
+ "\n"
1557 def cb_guest_to_pid(option
, opt
, val
, parser
):
1559 pids
= Tui
.get_pid_from_gname(val
)
1561 sys
.exit('Error while searching for guest "{}". Use "-p" to '
1562 'specify a pid instead?'.format(val
))
1564 sys
.exit('Error: No guest by the name "{}" found'.format(val
))
1566 sys
.exit('Error: Multiple processes found (pids: {}). Use "-p" '
1567 'to specify the desired pid'.format(" ".join(pids
)))
1568 parser
.values
.pid
= pids
[0]
1570 optparser
= optparse
.OptionParser(description
=description_text
,
1571 formatter
=PlainHelpFormatter())
1572 optparser
.add_option('-1', '--once', '--batch',
1573 action
='store_true',
1576 help='run in batch mode for one second',
1578 optparser
.add_option('-i', '--debugfs-include-past',
1579 action
='store_true',
1581 dest
='dbgfs_include_past',
1582 help='include all available data on past events for '
1585 optparser
.add_option('-l', '--log',
1586 action
='store_true',
1589 help='run in logging mode (like vmstat)',
1591 optparser
.add_option('-t', '--tracepoints',
1592 action
='store_true',
1595 help='retrieve statistics from tracepoints',
1597 optparser
.add_option('-d', '--debugfs',
1598 action
='store_true',
1601 help='retrieve statistics from debugfs',
1603 optparser
.add_option('-f', '--fields',
1607 help='''fields to display (regex)
1608 "-f help" for a list of available events''',
1610 optparser
.add_option('-p', '--pid',
1615 help='restrict statistics to pid',
1617 optparser
.add_option('-g', '--guest',
1622 help='restrict statistics to guest by name',
1623 callback
=cb_guest_to_pid
,
1625 options
, unkn
= optparser
.parse_args(sys
.argv
)
1627 sys
.exit('Error: Extra argument(s): ' + ' '.join(unkn
[1:]))
1629 # verify that we were passed a valid regex up front
1630 re
.compile(options
.fields
)
1632 sys
.exit('Error: "' + options
.fields
+ '" is not a valid regular '
1638 def check_access(options
):
1639 """Exits if the current user can't access all needed directories."""
1640 if not os
.path
.exists(PATH_DEBUGFS_TRACING
) and (options
.tracepoints
or
1641 not options
.debugfs
):
1642 sys
.stderr
.write("Please enable CONFIG_TRACING in your kernel "
1643 "when using the option -t (default).\n"
1644 "If it is enabled, make {0} readable by the "
1646 .format(PATH_DEBUGFS_TRACING
))
1647 if options
.tracepoints
:
1650 sys
.stderr
.write("Falling back to debugfs statistics!\n")
1651 options
.debugfs
= True
1657 def assign_globals():
1658 global PATH_DEBUGFS_KVM
1659 global PATH_DEBUGFS_TRACING
1662 for line
in open('/proc/mounts'):
1663 if line
.split(' ')[0] == 'debugfs':
1664 debugfs
= line
.split(' ')[1]
1667 sys
.stderr
.write("Please make sure that CONFIG_DEBUG_FS is enabled in "
1668 "your kernel, mounted and\nreadable by the current "
1670 "('mount -t debugfs debugfs /sys/kernel/debug')\n")
1673 PATH_DEBUGFS_KVM
= os
.path
.join(debugfs
, 'kvm')
1674 PATH_DEBUGFS_TRACING
= os
.path
.join(debugfs
, 'tracing')
1676 if not os
.path
.exists(PATH_DEBUGFS_KVM
):
1677 sys
.stderr
.write("Please make sure that CONFIG_KVM is enabled in "
1678 "your kernel and that the modules are loaded.\n")
1684 options
= get_options()
1685 options
= check_access(options
)
1687 if (options
.pid
> 0 and
1688 not os
.path
.isdir(os
.path
.join('/proc/',
1689 str(options
.pid
)))):
1690 sys
.stderr
.write('Did you use a (unsupported) tid instead of a pid?\n')
1691 sys
.exit('Specified pid does not exist.')
1693 stats
= Stats(options
)
1695 if options
.fields
== 'help':
1696 stats
.fields_filter
= None
1698 for key
in stats
.get().keys():
1699 event_list
.append(key
.split('(', 1)[0])
1700 sys
.stdout
.write(' ' + '\n '.join(sorted(set(event_list
))) + '\n')
1705 elif not options
.once
:
1706 with
Tui(stats
) as tui
:
1711 if __name__
== "__main__":