]>
git.ipfire.org Git - thirdparty/kernel/stable.git/blob - tools/kvm/kvm_stat/kvm_stat
2 # SPDX-License-Identifier: GPL-2.0-only
4 # top-like utility for displaying kvm statistics
6 # Copyright 2006-2008 Qumranet Technologies
7 # Copyright 2008-2011 Red Hat, Inc.
10 # Avi Kivity <avi@redhat.com>
12 """The kvm_stat module outputs statistics about running KVM VMs
14 Three different ways of output formatting are available:
15 - as a top-like text ui
16 - in a key -> value format
17 - in an all keys, all values format
19 The data is sampled from the KVM's debugfs entries and its perf events.
21 from __future__
import print_function
35 from collections
import defaultdict
, namedtuple
39 'EXTERNAL_INTERRUPT': 1,
41 'PENDING_INTERRUPT': 7,
65 'MWAIT_INSTRUCTION': 36,
66 'MONITOR_INSTRUCTION': 39,
67 'PAUSE_INSTRUCTION': 40,
68 'MCE_DURING_VMENTRY': 41,
69 'TPR_BELOW_THRESHOLD': 43,
110 'CR0_SEL_WRITE': 0x065,
134 'TASK_SWITCH': 0x07d,
135 'FERR_FREEZE': 0x07e,
154 # EC definition of HSR (from arch/arm64/include/asm/kvm_arm.h)
155 AARCH64_EXIT_REASONS
= {
193 # From include/uapi/linux/kvm.h, KVM_EXIT_xxx
194 USERSPACE_EXIT_REASONS
= {
202 'IRQ_WINDOW_OPEN': 7,
212 'INTERNAL_ERROR': 17,
223 'SET_FILTER': 0x40082406,
224 'ENABLE': 0x00002400,
225 'DISABLE': 0x00002401,
229 ENCODING
= locale
.getpreferredencoding(False)
230 TRACE_FILTER
= re
.compile(r
'^[^\(]*$')
234 """Encapsulates global architecture specific data.
236 Contains the performance event open syscall and ioctl numbers, as
237 well as the VM exit reasons for the architecture it runs on.
242 machine
= os
.uname()[4]
244 if machine
.startswith('ppc'):
246 elif machine
.startswith('aarch64'):
248 elif machine
.startswith('s390'):
252 for line
in open('/proc/cpuinfo'):
253 if not line
.startswith('flags'):
258 return ArchX86(VMX_EXIT_REASONS
)
260 return ArchX86(SVM_EXIT_REASONS
)
263 def tracepoint_is_child(self
, field
):
264 if (TRACE_FILTER
.match(field
)):
266 return field
.split('(', 1)[0]
270 def __init__(self
, exit_reasons
):
271 self
.sc_perf_evt_open
= 298
272 self
.ioctl_numbers
= IOCTL_NUMBERS
273 self
.exit_reasons
= exit_reasons
275 def debugfs_is_child(self
, field
):
276 """ Returns name of parent if 'field' is a child, None otherwise """
282 self
.sc_perf_evt_open
= 319
283 self
.ioctl_numbers
= IOCTL_NUMBERS
284 self
.ioctl_numbers
['ENABLE'] = 0x20002400
285 self
.ioctl_numbers
['DISABLE'] = 0x20002401
286 self
.ioctl_numbers
['RESET'] = 0x20002403
288 # PPC comes in 32 and 64 bit and some generated ioctl
289 # numbers depend on the wordsize.
290 char_ptr_size
= ctypes
.sizeof(ctypes
.c_char_p
)
291 self
.ioctl_numbers
['SET_FILTER'] = 0x80002406 | char_ptr_size
<< 16
292 self
.exit_reasons
= {}
294 def debugfs_is_child(self
, field
):
295 """ Returns name of parent if 'field' is a child, None otherwise """
301 self
.sc_perf_evt_open
= 241
302 self
.ioctl_numbers
= IOCTL_NUMBERS
303 self
.exit_reasons
= AARCH64_EXIT_REASONS
305 def debugfs_is_child(self
, field
):
306 """ Returns name of parent if 'field' is a child, None otherwise """
310 class ArchS390(Arch
):
312 self
.sc_perf_evt_open
= 331
313 self
.ioctl_numbers
= IOCTL_NUMBERS
314 self
.exit_reasons
= None
316 def debugfs_is_child(self
, field
):
317 """ Returns name of parent if 'field' is a child, None otherwise """
318 if field
.startswith('instruction_'):
319 return 'exit_instruction'
322 ARCH
= Arch
.get_arch()
325 class perf_event_attr(ctypes
.Structure
):
326 """Struct that holds the necessary data to set up a trace event.
328 For an extensive explanation see perf_event_open(2) and
329 include/uapi/linux/perf_event.h, struct perf_event_attr
331 All fields that are not initialized in the constructor are 0.
334 _fields_
= [('type', ctypes
.c_uint32
),
335 ('size', ctypes
.c_uint32
),
336 ('config', ctypes
.c_uint64
),
337 ('sample_freq', ctypes
.c_uint64
),
338 ('sample_type', ctypes
.c_uint64
),
339 ('read_format', ctypes
.c_uint64
),
340 ('flags', ctypes
.c_uint64
),
341 ('wakeup_events', ctypes
.c_uint32
),
342 ('bp_type', ctypes
.c_uint32
),
343 ('bp_addr', ctypes
.c_uint64
),
344 ('bp_len', ctypes
.c_uint64
),
348 super(self
.__class
__, self
).__init
__()
349 self
.type = PERF_TYPE_TRACEPOINT
350 self
.size
= ctypes
.sizeof(self
)
351 self
.read_format
= PERF_FORMAT_GROUP
354 PERF_TYPE_TRACEPOINT
= 2
355 PERF_FORMAT_GROUP
= 1 << 3
359 """Represents a perf event group."""
364 def add_event(self
, event
):
365 self
.events
.append(event
)
368 """Returns a dict with 'event name: value' for all events in the
371 Values are read by reading from the file descriptor of the
372 event that is the group leader. See perf_event_open(2) for
375 Read format for the used event configuration is:
377 u64 nr; /* The number of events */
379 u64 value; /* The value of the event */
384 length
= 8 * (1 + len(self
.events
))
385 read_format
= 'xxxxxxxx' + 'Q' * len(self
.events
)
386 return dict(zip([event
.name
for event
in self
.events
],
387 struct
.unpack(read_format
,
388 os
.read(self
.events
[0].fd
, length
))))
392 """Represents a performance event and manages its life cycle."""
393 def __init__(self
, name
, group
, trace_cpu
, trace_pid
, trace_point
,
394 trace_filter
, trace_set
='kvm'):
395 self
.libc
= ctypes
.CDLL('libc.so.6', use_errno
=True)
396 self
.syscall
= self
.libc
.syscall
399 self
._setup
_event
(group
, trace_cpu
, trace_pid
, trace_point
,
400 trace_filter
, trace_set
)
403 """Closes the event's file descriptor.
405 As no python file object was created for the file descriptor,
406 python will not reference count the descriptor and will not
407 close it itself automatically, so we do it.
413 def _perf_event_open(self
, attr
, pid
, cpu
, group_fd
, flags
):
414 """Wrapper for the sys_perf_evt_open() syscall.
416 Used to set up performance events, returns a file descriptor or -1
421 - struct perf_event_attr *
422 - pid or -1 to monitor all pids
423 - cpu number or -1 to monitor all cpus
424 - The file descriptor of the group leader or -1 to create a group.
428 return self
.syscall(ARCH
.sc_perf_evt_open
, ctypes
.pointer(attr
),
429 ctypes
.c_int(pid
), ctypes
.c_int(cpu
),
430 ctypes
.c_int(group_fd
), ctypes
.c_long(flags
))
432 def _setup_event_attribute(self
, trace_set
, trace_point
):
433 """Returns an initialized ctype perf_event_attr struct."""
435 id_path
= os
.path
.join(PATH_DEBUGFS_TRACING
, 'events', trace_set
,
438 event_attr
= perf_event_attr()
439 event_attr
.config
= int(open(id_path
).read())
442 def _setup_event(self
, group
, trace_cpu
, trace_pid
, trace_point
,
443 trace_filter
, trace_set
):
444 """Sets up the perf event in Linux.
446 Issues the syscall to register the event in the kernel and
447 then sets the optional filter.
451 event_attr
= self
._setup
_event
_attribute
(trace_set
, trace_point
)
453 # First event will be group leader.
456 # All others have to pass the leader's descriptor instead.
458 group_leader
= group
.events
[0].fd
460 fd
= self
._perf
_event
_open
(event_attr
, trace_pid
,
461 trace_cpu
, group_leader
, 0)
463 err
= ctypes
.get_errno()
464 raise OSError(err
, os
.strerror(err
),
465 'while calling sys_perf_event_open().')
468 fcntl
.ioctl(fd
, ARCH
.ioctl_numbers
['SET_FILTER'],
474 """Enables the trace event in the kernel.
476 Enabling the group leader makes reading counters from it and the
477 events under it possible.
480 fcntl
.ioctl(self
.fd
, ARCH
.ioctl_numbers
['ENABLE'], 0)
483 """Disables the trace event in the kernel.
485 Disabling the group leader makes reading all counters under it
489 fcntl
.ioctl(self
.fd
, ARCH
.ioctl_numbers
['DISABLE'], 0)
492 """Resets the count of the trace event in the kernel."""
493 fcntl
.ioctl(self
.fd
, ARCH
.ioctl_numbers
['RESET'], 0)
496 class Provider(object):
497 """Encapsulates functionalities used by all providers."""
498 def __init__(self
, pid
):
499 self
.child_events
= False
503 def is_field_wanted(fields_filter
, field
):
504 """Indicate whether field is valid according to fields_filter."""
505 if not fields_filter
:
507 return re
.match(fields_filter
, field
) is not None
511 """Returns os.walk() data for specified directory.
513 As it is only a wrapper it returns the same 3-tuple of (dirpath,
514 dirnames, filenames).
516 return next(os
.walk(path
))
519 class TracepointProvider(Provider
):
520 """Data provider for the stats class.
522 Manages the events/groups from which it acquires its data.
525 def __init__(self
, pid
, fields_filter
):
526 self
.group_leaders
= []
527 self
.filters
= self
._get
_filters
()
528 self
.update_fields(fields_filter
)
529 super(TracepointProvider
, self
).__init
__(pid
)
533 """Returns a dict of trace events, their filter ids and
534 the values that can be filtered.
536 Trace events can be filtered for special values by setting a
537 filter string via an ioctl. The string normally has the format
538 identifier==value. For each filter a new event will be created, to
539 be able to distinguish the events.
543 filters
['kvm_userspace_exit'] = ('reason', USERSPACE_EXIT_REASONS
)
544 if ARCH
.exit_reasons
:
545 filters
['kvm_exit'] = ('exit_reason', ARCH
.exit_reasons
)
548 def _get_available_fields(self
):
549 """Returns a list of available events of format 'event name(filter
552 All available events have directories under
553 /sys/kernel/debug/tracing/events/ which export information
554 about the specific event. Therefore, listing the dirs gives us
555 a list of all available events.
557 Some events like the vm exit reasons can be filtered for
558 specific values. To take account for that, the routine below
559 creates special fields with the following format:
560 event name(filter name)
563 path
= os
.path
.join(PATH_DEBUGFS_TRACING
, 'events', 'kvm')
564 fields
= self
.walkdir(path
)[1]
567 if field
in self
.filters
:
568 filter_name_
, filter_dicts
= self
.filters
[field
]
569 for name
in filter_dicts
:
570 extra
.append(field
+ '(' + name
+ ')')
574 def update_fields(self
, fields_filter
):
575 """Refresh fields, applying fields_filter"""
576 self
.fields
= [field
for field
in self
._get
_available
_fields
()
577 if self
.is_field_wanted(fields_filter
, field
)]
578 # add parents for child fields - otherwise we won't see any output!
579 for field
in self
._fields
:
580 parent
= ARCH
.tracepoint_is_child(field
)
581 if (parent
and parent
not in self
._fields
):
582 self
.fields
.append(parent
)
585 def _get_online_cpus():
586 """Returns a list of cpu id integers."""
587 def parse_int_list(list_string
):
588 """Returns an int list from a string of comma separated integers and
591 members
= list_string
.split(',')
593 for member
in members
:
594 if '-' not in member
:
595 integers
.append(int(member
))
597 int_range
= member
.split('-')
598 integers
.extend(range(int(int_range
[0]),
599 int(int_range
[1]) + 1))
603 with
open('/sys/devices/system/cpu/online') as cpu_list
:
604 cpu_string
= cpu_list
.readline()
605 return parse_int_list(cpu_string
)
607 def _setup_traces(self
):
608 """Creates all event and group objects needed to be able to retrieve
610 fields
= self
._get
_available
_fields
()
612 # Fetch list of all threads of the monitored pid, as qemu
613 # starts a thread for each vcpu.
614 path
= os
.path
.join('/proc', str(self
._pid
), 'task')
615 groupids
= self
.walkdir(path
)[1]
617 groupids
= self
._get
_online
_cpus
()
619 # The constant is needed as a buffer for python libs, std
620 # streams and other files that the script opens.
621 newlim
= len(groupids
) * len(fields
) + 50
623 softlim_
, hardlim
= resource
.getrlimit(resource
.RLIMIT_NOFILE
)
626 # Now we need CAP_SYS_RESOURCE, to increase the hard limit.
627 resource
.setrlimit(resource
.RLIMIT_NOFILE
, (newlim
, newlim
))
629 # Raising the soft limit is sufficient.
630 resource
.setrlimit(resource
.RLIMIT_NOFILE
, (newlim
, hardlim
))
633 sys
.exit("NOFILE rlimit could not be raised to {0}".format(newlim
))
635 for groupid
in groupids
:
640 match
= re
.match(r
'(.*)\((.*)\)', name
)
642 tracepoint
, sub
= match
.groups()
643 tracefilter
= ('%s==%d\0' %
644 (self
.filters
[tracepoint
][0],
645 self
.filters
[tracepoint
][1][sub
]))
647 # From perf_event_open(2):
648 # pid > 0 and cpu == -1
649 # This measures the specified process/thread on any CPU.
651 # pid == -1 and cpu >= 0
652 # This measures all processes/threads on the specified CPU.
653 trace_cpu
= groupid
if self
._pid
== 0 else -1
654 trace_pid
= int(groupid
) if self
._pid
!= 0 else -1
656 group
.add_event(Event(name
=name
,
660 trace_point
=tracepoint
,
661 trace_filter
=tracefilter
))
663 self
.group_leaders
.append(group
)
670 def fields(self
, fields
):
671 """Enables/disables the (un)wanted events"""
672 self
._fields
= fields
673 for group
in self
.group_leaders
:
674 for index
, event
in enumerate(group
.events
):
675 if event
.name
in fields
:
679 # Do not disable the group leader.
680 # It would disable all of its events.
690 """Changes the monitored pid by setting new traces."""
692 # The garbage collector will get rid of all Event/Group
693 # objects and open files after removing the references.
694 self
.group_leaders
= []
696 self
.fields
= self
._fields
698 def read(self
, by_guest
=0):
699 """Returns 'event name: current value' for all enabled events."""
700 ret
= defaultdict(int)
701 for group
in self
.group_leaders
:
702 for name
, val
in group
.read().items():
703 if name
not in self
._fields
:
705 parent
= ARCH
.tracepoint_is_child(name
)
712 """Reset all field counters"""
713 for group
in self
.group_leaders
:
714 for event
in group
.events
:
718 class DebugfsProvider(Provider
):
719 """Provides data from the files that KVM creates in the kvm debugfs
721 def __init__(self
, pid
, fields_filter
, include_past
):
722 self
.update_fields(fields_filter
)
726 super(DebugfsProvider
, self
).__init
__(pid
)
730 def _get_available_fields(self
):
731 """"Returns a list of available fields.
733 The fields are all available KVM debugfs files
736 return self
.walkdir(PATH_DEBUGFS_KVM
)[2]
738 def update_fields(self
, fields_filter
):
739 """Refresh fields, applying fields_filter"""
740 self
._fields
= [field
for field
in self
._get
_available
_fields
()
741 if self
.is_field_wanted(fields_filter
, field
)]
742 # add parents for child fields - otherwise we won't see any output!
743 for field
in self
._fields
:
744 parent
= ARCH
.debugfs_is_child(field
)
745 if (parent
and parent
not in self
._fields
):
746 self
.fields
.append(parent
)
753 def fields(self
, fields
):
754 self
._fields
= fields
765 vms
= self
.walkdir(PATH_DEBUGFS_KVM
)[1]
769 self
.paths
= list(filter(lambda x
: "{}-".format(pid
) in x
, vms
))
775 def _verify_paths(self
):
776 """Remove invalid paths"""
777 for path
in self
.paths
:
778 if not os
.path
.exists(os
.path
.join(PATH_DEBUGFS_KVM
, path
)):
779 self
.paths
.remove(path
)
782 def read(self
, reset
=0, by_guest
=0):
783 """Returns a dict with format:'file name / field -> current value'.
787 1 reset field counts to 0
788 2 restore the original field counts
793 # If no debugfs filtering support is available, then don't read.
801 for entry
in os
.walk(PATH_DEBUGFS_KVM
):
805 for field
in self
._fields
:
806 value
= self
._read
_field
(field
, path
)
809 self
._baseline
[key
] = value
811 self
._baseline
[key
] = 0
812 if self
._baseline
.get(key
, -1) == -1:
813 self
._baseline
[key
] = value
814 parent
= ARCH
.debugfs_is_child(field
)
816 field
= field
+ ' ' + parent
819 field
= key
.split('-')[0] # set 'field' to 'pid'
820 increment
= value
- self
._baseline
.get(key
, 0)
822 results
[field
] += increment
824 results
[field
] = increment
828 def _read_field(self
, field
, path
):
829 """Returns the value of a single field from a specific VM."""
831 return int(open(os
.path
.join(PATH_DEBUGFS_KVM
,
839 """Reset field counters"""
844 """Reset field counters"""
849 EventStat
= namedtuple('EventStat', ['value', 'delta'])
853 """Manages the data providers and the data they provide.
855 It is used to set filters on the provider's data and collect all
859 def __init__(self
, options
):
860 self
.providers
= self
._get
_providers
(options
)
861 self
._pid
_filter
= options
.pid
862 self
._fields
_filter
= options
.fields
864 self
._child
_events
= False
866 def _get_providers(self
, options
):
867 """Returns a list of data providers depending on the passed options."""
871 providers
.append(DebugfsProvider(options
.pid
, options
.fields
,
872 options
.dbgfs_include_past
))
873 if options
.tracepoints
or not providers
:
874 providers
.append(TracepointProvider(options
.pid
, options
.fields
))
878 def _update_provider_filters(self
):
879 """Propagates fields filters to providers."""
880 # As we reset the counters when updating the fields we can
881 # also clear the cache of old values.
883 for provider
in self
.providers
:
884 provider
.update_fields(self
._fields
_filter
)
888 for provider
in self
.providers
:
892 def fields_filter(self
):
893 return self
._fields
_filter
895 @fields_filter.setter
896 def fields_filter(self
, fields_filter
):
897 if fields_filter
!= self
._fields
_filter
:
898 self
._fields
_filter
= fields_filter
899 self
._update
_provider
_filters
()
902 def pid_filter(self
):
903 return self
._pid
_filter
906 def pid_filter(self
, pid
):
907 if pid
!= self
._pid
_filter
:
908 self
._pid
_filter
= pid
910 for provider
in self
.providers
:
911 provider
.pid
= self
._pid
_filter
914 def child_events(self
):
915 return self
._child
_events
918 def child_events(self
, val
):
919 self
._child
_events
= val
920 for provider
in self
.providers
:
921 provider
.child_events
= val
923 def get(self
, by_guest
=0):
924 """Returns a dict with field -> (value, delta to last value) of all
927 * plain: 'key' is event name
928 * child-parent: 'key' is in format '<child> <parent>'
929 * pid: 'key' is the pid of the guest, and the record contains the
930 aggregated event data
931 These formats are generated by the providers, and handled in class TUI.
933 for provider
in self
.providers
:
934 new
= provider
.read(by_guest
=by_guest
)
936 oldval
= self
.values
.get(key
, EventStat(0, 0)).value
937 newval
= new
.get(key
, 0)
938 newdelta
= newval
- oldval
939 self
.values
[key
] = EventStat(newval
, newdelta
)
942 def toggle_display_guests(self
, to_pid
):
943 """Toggle between collection of stats by individual event and by
946 Events reported by DebugfsProvider change when switching to/from
947 reading by guest values. Hence we have to remove the excess event
948 names from self.values.
951 if any(isinstance(ins
, TracepointProvider
) for ins
in self
.providers
):
954 for provider
in self
.providers
:
955 if isinstance(provider
, DebugfsProvider
):
956 for key
in provider
.fields
:
957 if key
in self
.values
.keys():
960 oldvals
= self
.values
.copy()
964 # Update oldval (see get())
970 MAX_GUEST_NAME_LEN
= 48
976 """Instruments curses to draw a nice text ui."""
977 def __init__(self
, stats
):
980 self
._delay
_initial
= 0.25
981 self
._delay
_regular
= DELAY_DEFAULT
982 self
._sorting
= SORT_DEFAULT
983 self
._display
_guests
= 0
986 """Initialises curses for later use. Based on curses.wrapper
987 implementation from the Python standard library."""
988 self
.screen
= curses
.initscr()
992 # The try/catch works around a minor bit of
993 # over-conscientiousness in the curses module, the error
994 # return from C start_color() is ignorable.
1000 # Hide cursor in extra statement as some monochrome terminals
1001 # might support hiding but not colors.
1004 except curses
.error
:
1007 curses
.use_default_colors()
1010 def __exit__(self
, *exception
):
1011 """Resets the terminal to its normal state. Based on curses.wrapper
1012 implementation from the Python standard library."""
1014 self
.screen
.keypad(0)
1020 def get_all_gnames():
1021 """Returns a list of (pid, gname) tuples of all running guests"""
1024 child
= subprocess
.Popen(['ps', '-A', '--format', 'pid,args'],
1025 stdout
=subprocess
.PIPE
)
1028 for line
in child
.stdout
:
1029 line
= line
.decode(ENCODING
).lstrip().split(' ', 1)
1030 # perform a sanity check before calling the more expensive
1031 # function to possibly extract the guest name
1032 if ' -name ' in line
[1]:
1033 res
.append((line
[0], Tui
.get_gname_from_pid(line
[0])))
1034 child
.stdout
.close()
1038 def _print_all_gnames(self
, row
):
1039 """Print a list of all running guests along with their pids."""
1040 self
.screen
.addstr(row
, 2, '%8s %-60s' %
1041 ('Pid', 'Guest Name (fuzzy list, might be '
1046 for line
in self
.get_all_gnames():
1047 self
.screen
.addstr(row
, 2, '%8s %-60s' % (line
[0], line
[1]))
1049 if row
>= self
.screen
.getmaxyx()[0]:
1052 self
.screen
.addstr(row
+ 1, 2, 'Not available')
1055 def get_pid_from_gname(gname
):
1056 """Fuzzy function to convert guest name to QEMU process pid.
1058 Returns a list of potential pids, can be empty if no match found.
1059 Throws an exception on processing errors.
1063 for line
in Tui
.get_all_gnames():
1064 if gname
== line
[1]:
1065 pids
.append(int(line
[0]))
1070 def get_gname_from_pid(pid
):
1071 """Returns the guest name for a QEMU process pid.
1073 Extracts the guest name from the QEMU comma line by processing the
1074 '-name' option. Will also handle names specified out of sequence.
1079 line
= open('/proc/{}/cmdline'
1080 .format(pid
), 'r').read().split('\0')
1081 parms
= line
[line
.index('-name') + 1].split(',')
1083 # commas are escaped (i.e. ',,'), hence e.g. 'foo,bar' results
1084 # in # ['foo', '', 'bar'], which we revert here
1085 idx
= parms
.index('')
1086 parms
[idx
- 1] += ',' + parms
[idx
+ 1]
1087 del parms
[idx
:idx
+2]
1088 # the '-name' switch allows for two ways to specify the guest name,
1089 # where the plain name overrides the name specified via 'guest='
1094 if arg
[:6] == 'guest=':
1096 except (ValueError, IOError, IndexError):
1101 def _update_pid(self
, pid
):
1102 """Propagates pid selection to stats object."""
1103 self
.screen
.addstr(4, 1, 'Updating pid filter...')
1104 self
.screen
.refresh()
1105 self
.stats
.pid_filter
= pid
1107 def _refresh_header(self
, pid
=None):
1108 """Refreshes the header."""
1110 pid
= self
.stats
.pid_filter
1112 gname
= self
.get_gname_from_pid(pid
)
1115 gname
= ('({})'.format(gname
[:MAX_GUEST_NAME_LEN
] + '...'
1116 if len(gname
) > MAX_GUEST_NAME_LEN
1119 self
._headline
= 'kvm statistics - pid {0} {1}'.format(pid
, gname
)
1121 self
._headline
= 'kvm statistics - summary'
1122 self
.screen
.addstr(0, 0, self
._headline
, curses
.A_BOLD
)
1123 if self
.stats
.fields_filter
:
1124 regex
= self
.stats
.fields_filter
1125 if len(regex
) > MAX_REGEX_LEN
:
1126 regex
= regex
[:MAX_REGEX_LEN
] + '...'
1127 self
.screen
.addstr(1, 17, 'regex filter: {0}'.format(regex
))
1128 if self
._display
_guests
:
1129 col_name
= 'Guest Name'
1132 self
.screen
.addstr(2, 1, '%-40s %10s%7s %8s' %
1133 (col_name
, 'Total', '%Total', 'CurAvg/s'),
1135 self
.screen
.addstr(4, 1, 'Collecting data...')
1136 self
.screen
.refresh()
1138 def _refresh_body(self
, sleeptime
):
1139 def insert_child(sorted_items
, child
, values
, parent
):
1140 num
= len(sorted_items
)
1141 for i
in range(0, num
):
1142 # only add child if parent is present
1143 if parent
.startswith(sorted_items
[i
][0]):
1144 sorted_items
.insert(i
+ 1, (' ' + child
, values
))
1146 def get_sorted_events(self
, stats
):
1147 """ separate parent and child events """
1148 if self
._sorting
== SORT_DEFAULT
:
1150 # sort by (delta value, overall value)
1152 return (v
.delta
, v
.value
)
1155 # sort by overall value
1161 # we can't rule out child events to appear prior to parents even
1162 # when sorted - separate out all children first, and add in later
1163 for key
, values
in sorted(stats
.items(), key
=sortkey
,
1165 if values
== (0, 0):
1167 if key
.find(' ') != -1:
1168 if not self
.stats
.child_events
:
1170 childs
.insert(0, (key
, values
))
1172 sorted_items
.append((key
, values
))
1173 if self
.stats
.child_events
:
1174 for key
, values
in childs
:
1175 (child
, parent
) = key
.split(' ')
1176 insert_child(sorted_items
, child
, values
, parent
)
1180 if not self
._is
_running
_guest
(self
.stats
.pid_filter
):
1182 try: # ...to identify the guest by name in case it's back
1183 pids
= self
.get_pid_from_gname(self
._gname
)
1185 self
._refresh
_header
(pids
[0])
1186 self
._update
_pid
(pids
[0])
1190 self
._display
_guest
_dead
()
1191 # leave final data on screen
1194 self
.screen
.move(row
, 0)
1195 self
.screen
.clrtobot()
1196 stats
= self
.stats
.get(self
._display
_guests
)
1199 for key
, values
in stats
.items():
1200 if self
._display
_guests
:
1201 if self
.get_gname_from_pid(key
):
1202 total
+= values
.value
1204 if not key
.find(' ') != -1:
1205 total
+= values
.value
1207 ctotal
+= values
.value
1209 # we don't have any fields, or all non-child events are filtered
1215 guest_removed
= False
1216 for key
, values
in get_sorted_events(self
, stats
):
1217 if row
>= self
.screen
.getmaxyx()[0] - 1 or values
== (0, 0):
1219 if self
._display
_guests
:
1220 key
= self
.get_gname_from_pid(key
)
1223 cur
= int(round(values
.delta
/ sleeptime
)) if values
.delta
else 0
1225 guest_removed
= True
1229 tcur
+= values
.delta
1230 ptotal
= values
.value
1234 self
.screen
.addstr(row
, 1, '%-40s %10d%7.1f %8s' % (key
,
1236 values
.value
* 100 / float(ltotal
), cur
))
1240 self
.screen
.addstr(4, 1, 'Guest removed, updating...')
1242 self
.screen
.addstr(4, 1, 'No matching events reported yet')
1244 tavg
= int(round(tcur
/ sleeptime
)) if tcur
> 0 else ''
1245 self
.screen
.addstr(row
, 1, '%-40s %10d %8s' %
1246 ('Total', total
, tavg
), curses
.A_BOLD
)
1247 self
.screen
.refresh()
1249 def _display_guest_dead(self
):
1250 marker
= ' Guest is DEAD '
1251 y
= min(len(self
._headline
), 80 - len(marker
))
1252 self
.screen
.addstr(0, y
, marker
, curses
.A_BLINK | curses
.A_STANDOUT
)
1254 def _show_msg(self
, text
):
1255 """Display message centered text and exit on key press"""
1256 hint
= 'Press any key to continue'
1259 (x
, term_width
) = self
.screen
.getmaxyx()
1262 start
= (term_width
- len(line
)) // 2
1263 self
.screen
.addstr(row
, start
, line
)
1265 self
.screen
.addstr(row
+ 1, (term_width
- len(hint
)) // 2, hint
,
1267 self
.screen
.getkey()
1269 def _show_help_interactive(self
):
1270 """Display help with list of interactive commands"""
1271 msg
= (' b toggle events by guests (debugfs only, honors'
1274 ' f filter by regular expression',
1275 ' g filter by guest name/PID',
1276 ' h display interactive commands reference',
1277 ' o toggle sorting order (Total vs CurAvg/s)',
1278 ' p filter by guest name/PID',
1281 ' s set update interval',
1282 ' x toggle reporting of stats for individual child trace'
1284 'Any other key refreshes statistics immediately')
1287 self
.screen
.addstr(0, 0, "Interactive commands reference",
1289 self
.screen
.addstr(2, 0, "Press any key to exit", curses
.A_STANDOUT
)
1292 self
.screen
.addstr(row
, 0, line
)
1294 self
.screen
.getkey()
1295 self
._refresh
_header
()
1297 def _show_filter_selection(self
):
1298 """Draws filter selection mask.
1300 Asks for a valid regex and sets the fields filter accordingly.
1306 self
.screen
.addstr(0, 0,
1307 "Show statistics for events matching a regex.",
1309 self
.screen
.addstr(2, 0,
1310 "Current regex: {0}"
1311 .format(self
.stats
.fields_filter
))
1312 self
.screen
.addstr(5, 0, msg
)
1313 self
.screen
.addstr(3, 0, "New regex: ")
1315 regex
= self
.screen
.getstr().decode(ENCODING
)
1318 self
.stats
.fields_filter
= ''
1319 self
._refresh
_header
()
1323 self
.stats
.fields_filter
= regex
1324 self
._refresh
_header
()
1327 msg
= '"' + regex
+ '": Not a valid regular expression'
1330 def _show_set_update_interval(self
):
1331 """Draws update interval selection mask."""
1335 self
.screen
.addstr(0, 0, 'Set update interval (defaults to %.1fs).' %
1336 DELAY_DEFAULT
, curses
.A_BOLD
)
1337 self
.screen
.addstr(4, 0, msg
)
1338 self
.screen
.addstr(2, 0, 'Change delay from %.1fs to ' %
1339 self
._delay
_regular
)
1341 val
= self
.screen
.getstr().decode(ENCODING
)
1348 msg
= '"' + str(val
) + '": Value must be >=0.1'
1351 msg
= '"' + str(val
) + '": Value must be <=25.5'
1354 delay
= DELAY_DEFAULT
1355 self
._delay
_regular
= delay
1359 msg
= '"' + str(val
) + '": Invalid value'
1360 self
._refresh
_header
()
1362 def _is_running_guest(self
, pid
):
1363 """Check if pid is still a running process."""
1366 return os
.path
.isdir(os
.path
.join('/proc/', str(pid
)))
1368 def _show_vm_selection_by_guest(self
):
1369 """Draws guest selection mask.
1371 Asks for a guest name or pid until a valid guest name or '' is entered.
1377 self
.screen
.addstr(0, 0,
1378 'Show statistics for specific guest or pid.',
1380 self
.screen
.addstr(1, 0,
1381 'This might limit the shown data to the trace '
1383 self
.screen
.addstr(5, 0, msg
)
1384 self
._print
_all
_gnames
(7)
1387 self
.screen
.addstr(3, 0, "Guest or pid [ENTER exits]: ")
1388 guest
= self
.screen
.getstr().decode(ENCODING
)
1392 if not guest
or guest
== '0':
1395 if not self
._is
_running
_guest
(guest
):
1396 msg
= '"' + guest
+ '": Not a running process'
1402 pids
= self
.get_pid_from_gname(guest
)
1404 msg
= '"' + guest
+ '": Internal error while searching, ' \
1405 'use pid filter instead'
1408 msg
= '"' + guest
+ '": Not an active guest'
1411 msg
= '"' + guest
+ '": Multiple matches found, use pid ' \
1417 self
._refresh
_header
(pid
)
1418 self
._update
_pid
(pid
)
1420 def show_stats(self
):
1421 """Refreshes the screen and processes user input."""
1422 sleeptime
= self
._delay
_initial
1423 self
._refresh
_header
()
1424 start
= 0.0 # result based on init value never appears on screen
1426 self
._refresh
_body
(time
.time() - start
)
1427 curses
.halfdelay(int(sleeptime
* 10))
1429 sleeptime
= self
._delay
_regular
1431 char
= self
.screen
.getkey()
1433 self
._display
_guests
= not self
._display
_guests
1434 if self
.stats
.toggle_display_guests(self
._display
_guests
):
1435 self
._show
_msg
(['Command not available with '
1436 'tracepoints enabled', 'Restart with '
1437 'debugfs only (see option \'-d\') and '
1439 self
._display
_guests
= not self
._display
_guests
1440 self
._refresh
_header
()
1442 self
.stats
.fields_filter
= ''
1443 self
._refresh
_header
(0)
1447 self
._show
_filter
_selection
()
1449 sleeptime
= self
._delay
_initial
1450 if char
== 'g' or char
== 'p':
1451 self
._show
_vm
_selection
_by
_guest
()
1452 sleeptime
= self
._delay
_initial
1454 self
._show
_help
_interactive
()
1456 self
._sorting
= not self
._sorting
1463 self
._show
_set
_update
_interval
()
1465 sleeptime
= self
._delay
_initial
1467 self
.stats
.child_events
= not self
.stats
.child_events
1468 except KeyboardInterrupt:
1470 except curses
.error
:
1475 """Prints statistics in a key, value format."""
1480 for key
, values
in sorted(s
.items()):
1481 print('%-42s%10d%10d' % (key
.split(' ')[0], values
.value
,
1483 except KeyboardInterrupt:
1488 """Prints statistics as reiterating key block, multiple value blocks."""
1489 keys
= sorted(stats
.get().keys())
1493 print(key
.split(' ')[0], end
=' ')
1499 print(' %9d' % s
[key
].delta
, end
=' ')
1506 if line
% banner_repeat
== 0:
1510 except KeyboardInterrupt:
1515 """Returns processed program arguments."""
1516 description_text
= """
1517 This script displays various statistics about VMs running under KVM.
1518 The statistics are gathered from the KVM debugfs entries and / or the
1519 currently available perf traces.
1521 The monitoring takes additional cpu cycles and might affect the VM's
1529 - /proc/sys/kernel/perf_event_paranoid < 1 if user has no
1530 CAP_SYS_ADMIN and perf events are used.
1531 - CAP_SYS_RESOURCE if the hard limit is not high enough to allow
1532 the large number of files that are possibly opened.
1534 Interactive Commands:
1535 b toggle events by guests (debugfs only, honors filters)
1537 f filter by regular expression
1538 g filter by guest name
1539 h display interactive commands reference
1540 o toggle sorting order (Total vs CurAvg/s)
1544 s set update interval
1545 x toggle reporting of stats for individual child trace events
1546 Press any other key to refresh statistics immediately.
1547 """ % (PATH_DEBUGFS_KVM
, PATH_DEBUGFS_TRACING
)
1549 class PlainHelpFormatter(optparse
.IndentedHelpFormatter
):
1550 def format_description(self
, description
):
1552 return description
+ "\n"
1556 def cb_guest_to_pid(option
, opt
, val
, parser
):
1558 pids
= Tui
.get_pid_from_gname(val
)
1560 sys
.exit('Error while searching for guest "{}". Use "-p" to '
1561 'specify a pid instead?'.format(val
))
1563 sys
.exit('Error: No guest by the name "{}" found'.format(val
))
1565 sys
.exit('Error: Multiple processes found (pids: {}). Use "-p" '
1566 'to specify the desired pid'.format(" ".join(pids
)))
1567 parser
.values
.pid
= pids
[0]
1569 optparser
= optparse
.OptionParser(description
=description_text
,
1570 formatter
=PlainHelpFormatter())
1571 optparser
.add_option('-1', '--once', '--batch',
1572 action
='store_true',
1575 help='run in batch mode for one second',
1577 optparser
.add_option('-i', '--debugfs-include-past',
1578 action
='store_true',
1580 dest
='dbgfs_include_past',
1581 help='include all available data on past events for '
1584 optparser
.add_option('-l', '--log',
1585 action
='store_true',
1588 help='run in logging mode (like vmstat)',
1590 optparser
.add_option('-t', '--tracepoints',
1591 action
='store_true',
1594 help='retrieve statistics from tracepoints',
1596 optparser
.add_option('-d', '--debugfs',
1597 action
='store_true',
1600 help='retrieve statistics from debugfs',
1602 optparser
.add_option('-f', '--fields',
1606 help='''fields to display (regex)
1607 "-f help" for a list of available events''',
1609 optparser
.add_option('-p', '--pid',
1614 help='restrict statistics to pid',
1616 optparser
.add_option('-g', '--guest',
1621 help='restrict statistics to guest by name',
1622 callback
=cb_guest_to_pid
,
1624 options
, unkn
= optparser
.parse_args(sys
.argv
)
1626 sys
.exit('Error: Extra argument(s): ' + ' '.join(unkn
[1:]))
1628 # verify that we were passed a valid regex up front
1629 re
.compile(options
.fields
)
1631 sys
.exit('Error: "' + options
.fields
+ '" is not a valid regular '
1637 def check_access(options
):
1638 """Exits if the current user can't access all needed directories."""
1639 if not os
.path
.exists(PATH_DEBUGFS_TRACING
) and (options
.tracepoints
or
1640 not options
.debugfs
):
1641 sys
.stderr
.write("Please enable CONFIG_TRACING in your kernel "
1642 "when using the option -t (default).\n"
1643 "If it is enabled, make {0} readable by the "
1645 .format(PATH_DEBUGFS_TRACING
))
1646 if options
.tracepoints
:
1649 sys
.stderr
.write("Falling back to debugfs statistics!\n")
1650 options
.debugfs
= True
1656 def assign_globals():
1657 global PATH_DEBUGFS_KVM
1658 global PATH_DEBUGFS_TRACING
1661 for line
in open('/proc/mounts'):
1662 if line
.split(' ')[0] == 'debugfs':
1663 debugfs
= line
.split(' ')[1]
1666 sys
.stderr
.write("Please make sure that CONFIG_DEBUG_FS is enabled in "
1667 "your kernel, mounted and\nreadable by the current "
1669 "('mount -t debugfs debugfs /sys/kernel/debug')\n")
1672 PATH_DEBUGFS_KVM
= os
.path
.join(debugfs
, 'kvm')
1673 PATH_DEBUGFS_TRACING
= os
.path
.join(debugfs
, 'tracing')
1675 if not os
.path
.exists(PATH_DEBUGFS_KVM
):
1676 sys
.stderr
.write("Please make sure that CONFIG_KVM is enabled in "
1677 "your kernel and that the modules are loaded.\n")
1683 options
= get_options()
1684 options
= check_access(options
)
1686 if (options
.pid
> 0 and
1687 not os
.path
.isdir(os
.path
.join('/proc/',
1688 str(options
.pid
)))):
1689 sys
.stderr
.write('Did you use a (unsupported) tid instead of a pid?\n')
1690 sys
.exit('Specified pid does not exist.')
1692 stats
= Stats(options
)
1694 if options
.fields
== 'help':
1695 stats
.fields_filter
= None
1697 for key
in stats
.get().keys():
1698 event_list
.append(key
.split('(', 1)[0])
1699 sys
.stdout
.write(' ' + '\n '.join(sorted(set(event_list
))) + '\n')
1704 elif not options
.once
:
1705 with
Tui(stats
) as tui
:
1710 if __name__
== "__main__":