]>
git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blob - tools/kvm/kvm_stat/kvm_stat
e3f0becb6632e96d181b7f545ff800a03a3d7be6
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)
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
)
265 def __init__(self
, exit_reasons
):
266 self
.sc_perf_evt_open
= 298
267 self
.ioctl_numbers
= IOCTL_NUMBERS
268 self
.exit_reasons
= exit_reasons
273 self
.sc_perf_evt_open
= 319
274 self
.ioctl_numbers
= IOCTL_NUMBERS
275 self
.ioctl_numbers
['ENABLE'] = 0x20002400
276 self
.ioctl_numbers
['DISABLE'] = 0x20002401
277 self
.ioctl_numbers
['RESET'] = 0x20002403
279 # PPC comes in 32 and 64 bit and some generated ioctl
280 # numbers depend on the wordsize.
281 char_ptr_size
= ctypes
.sizeof(ctypes
.c_char_p
)
282 self
.ioctl_numbers
['SET_FILTER'] = 0x80002406 | char_ptr_size
<< 16
283 self
.exit_reasons
= {}
288 self
.sc_perf_evt_open
= 241
289 self
.ioctl_numbers
= IOCTL_NUMBERS
290 self
.exit_reasons
= AARCH64_EXIT_REASONS
293 class ArchS390(Arch
):
295 self
.sc_perf_evt_open
= 331
296 self
.ioctl_numbers
= IOCTL_NUMBERS
297 self
.exit_reasons
= None
299 ARCH
= Arch
.get_arch()
302 class perf_event_attr(ctypes
.Structure
):
303 """Struct that holds the necessary data to set up a trace event.
305 For an extensive explanation see perf_event_open(2) and
306 include/uapi/linux/perf_event.h, struct perf_event_attr
308 All fields that are not initialized in the constructor are 0.
311 _fields_
= [('type', ctypes
.c_uint32
),
312 ('size', ctypes
.c_uint32
),
313 ('config', ctypes
.c_uint64
),
314 ('sample_freq', ctypes
.c_uint64
),
315 ('sample_type', ctypes
.c_uint64
),
316 ('read_format', ctypes
.c_uint64
),
317 ('flags', ctypes
.c_uint64
),
318 ('wakeup_events', ctypes
.c_uint32
),
319 ('bp_type', ctypes
.c_uint32
),
320 ('bp_addr', ctypes
.c_uint64
),
321 ('bp_len', ctypes
.c_uint64
),
325 super(self
.__class
__, self
).__init
__()
326 self
.type = PERF_TYPE_TRACEPOINT
327 self
.size
= ctypes
.sizeof(self
)
328 self
.read_format
= PERF_FORMAT_GROUP
331 PERF_TYPE_TRACEPOINT
= 2
332 PERF_FORMAT_GROUP
= 1 << 3
334 PATH_DEBUGFS_TRACING
= '/sys/kernel/debug/tracing'
335 PATH_DEBUGFS_KVM
= '/sys/kernel/debug/kvm'
339 """Represents a perf event group."""
344 def add_event(self
, event
):
345 self
.events
.append(event
)
348 """Returns a dict with 'event name: value' for all events in the
351 Values are read by reading from the file descriptor of the
352 event that is the group leader. See perf_event_open(2) for
355 Read format for the used event configuration is:
357 u64 nr; /* The number of events */
359 u64 value; /* The value of the event */
364 length
= 8 * (1 + len(self
.events
))
365 read_format
= 'xxxxxxxx' + 'Q' * len(self
.events
)
366 return dict(zip([event
.name
for event
in self
.events
],
367 struct
.unpack(read_format
,
368 os
.read(self
.events
[0].fd
, length
))))
372 """Represents a performance event and manages its life cycle."""
373 def __init__(self
, name
, group
, trace_cpu
, trace_pid
, trace_point
,
374 trace_filter
, trace_set
='kvm'):
375 self
.libc
= ctypes
.CDLL('libc.so.6', use_errno
=True)
376 self
.syscall
= self
.libc
.syscall
379 self
.setup_event(group
, trace_cpu
, trace_pid
, trace_point
,
380 trace_filter
, trace_set
)
383 """Closes the event's file descriptor.
385 As no python file object was created for the file descriptor,
386 python will not reference count the descriptor and will not
387 close it itself automatically, so we do it.
393 def perf_event_open(self
, attr
, pid
, cpu
, group_fd
, flags
):
394 """Wrapper for the sys_perf_evt_open() syscall.
396 Used to set up performance events, returns a file descriptor or -1
401 - struct perf_event_attr *
402 - pid or -1 to monitor all pids
403 - cpu number or -1 to monitor all cpus
404 - The file descriptor of the group leader or -1 to create a group.
408 return self
.syscall(ARCH
.sc_perf_evt_open
, ctypes
.pointer(attr
),
409 ctypes
.c_int(pid
), ctypes
.c_int(cpu
),
410 ctypes
.c_int(group_fd
), ctypes
.c_long(flags
))
412 def setup_event_attribute(self
, trace_set
, trace_point
):
413 """Returns an initialized ctype perf_event_attr struct."""
415 id_path
= os
.path
.join(PATH_DEBUGFS_TRACING
, 'events', trace_set
,
418 event_attr
= perf_event_attr()
419 event_attr
.config
= int(open(id_path
).read())
422 def setup_event(self
, group
, trace_cpu
, trace_pid
, trace_point
,
423 trace_filter
, trace_set
):
424 """Sets up the perf event in Linux.
426 Issues the syscall to register the event in the kernel and
427 then sets the optional filter.
431 event_attr
= self
.setup_event_attribute(trace_set
, trace_point
)
433 # First event will be group leader.
436 # All others have to pass the leader's descriptor instead.
438 group_leader
= group
.events
[0].fd
440 fd
= self
.perf_event_open(event_attr
, trace_pid
,
441 trace_cpu
, group_leader
, 0)
443 err
= ctypes
.get_errno()
444 raise OSError(err
, os
.strerror(err
),
445 'while calling sys_perf_event_open().')
448 fcntl
.ioctl(fd
, ARCH
.ioctl_numbers
['SET_FILTER'],
454 """Enables the trace event in the kernel.
456 Enabling the group leader makes reading counters from it and the
457 events under it possible.
460 fcntl
.ioctl(self
.fd
, ARCH
.ioctl_numbers
['ENABLE'], 0)
463 """Disables the trace event in the kernel.
465 Disabling the group leader makes reading all counters under it
469 fcntl
.ioctl(self
.fd
, ARCH
.ioctl_numbers
['DISABLE'], 0)
472 """Resets the count of the trace event in the kernel."""
473 fcntl
.ioctl(self
.fd
, ARCH
.ioctl_numbers
['RESET'], 0)
476 class Provider(object):
477 """Encapsulates functionalities used by all providers."""
479 def is_field_wanted(fields_filter
, field
):
480 """Indicate whether field is valid according to fields_filter."""
481 if not fields_filter
:
483 return re
.match(fields_filter
, field
) is not None
487 """Returns os.walk() data for specified directory.
489 As it is only a wrapper it returns the same 3-tuple of (dirpath,
490 dirnames, filenames).
492 return next(os
.walk(path
))
495 class TracepointProvider(Provider
):
496 """Data provider for the stats class.
498 Manages the events/groups from which it acquires its data.
501 def __init__(self
, pid
, fields_filter
):
502 self
.group_leaders
= []
503 self
.filters
= self
.get_filters()
504 self
.update_fields(fields_filter
)
509 """Returns a dict of trace events, their filter ids and
510 the values that can be filtered.
512 Trace events can be filtered for special values by setting a
513 filter string via an ioctl. The string normally has the format
514 identifier==value. For each filter a new event will be created, to
515 be able to distinguish the events.
519 filters
['kvm_userspace_exit'] = ('reason', USERSPACE_EXIT_REASONS
)
520 if ARCH
.exit_reasons
:
521 filters
['kvm_exit'] = ('exit_reason', ARCH
.exit_reasons
)
524 def get_available_fields(self
):
525 """Returns a list of available event's of format 'event name(filter
528 All available events have directories under
529 /sys/kernel/debug/tracing/events/ which export information
530 about the specific event. Therefore, listing the dirs gives us
531 a list of all available events.
533 Some events like the vm exit reasons can be filtered for
534 specific values. To take account for that, the routine below
535 creates special fields with the following format:
536 event name(filter name)
539 path
= os
.path
.join(PATH_DEBUGFS_TRACING
, 'events', 'kvm')
540 fields
= self
.walkdir(path
)[1]
543 if field
in self
.filters
:
544 filter_name_
, filter_dicts
= self
.filters
[field
]
545 for name
in filter_dicts
:
546 extra
.append(field
+ '(' + name
+ ')')
550 def update_fields(self
, fields_filter
):
551 """Refresh fields, applying fields_filter"""
552 self
.fields
= [field
for field
in self
.get_available_fields()
553 if self
.is_field_wanted(fields_filter
, field
)]
556 def get_online_cpus():
557 """Returns a list of cpu id integers."""
558 def parse_int_list(list_string
):
559 """Returns an int list from a string of comma separated integers and
562 members
= list_string
.split(',')
564 for member
in members
:
565 if '-' not in member
:
566 integers
.append(int(member
))
568 int_range
= member
.split('-')
569 integers
.extend(range(int(int_range
[0]),
570 int(int_range
[1]) + 1))
574 with
open('/sys/devices/system/cpu/online') as cpu_list
:
575 cpu_string
= cpu_list
.readline()
576 return parse_int_list(cpu_string
)
578 def setup_traces(self
):
579 """Creates all event and group objects needed to be able to retrieve
581 fields
= self
.get_available_fields()
583 # Fetch list of all threads of the monitored pid, as qemu
584 # starts a thread for each vcpu.
585 path
= os
.path
.join('/proc', str(self
._pid
), 'task')
586 groupids
= self
.walkdir(path
)[1]
588 groupids
= self
.get_online_cpus()
590 # The constant is needed as a buffer for python libs, std
591 # streams and other files that the script opens.
592 newlim
= len(groupids
) * len(fields
) + 50
594 softlim_
, hardlim
= resource
.getrlimit(resource
.RLIMIT_NOFILE
)
597 # Now we need CAP_SYS_RESOURCE, to increase the hard limit.
598 resource
.setrlimit(resource
.RLIMIT_NOFILE
, (newlim
, newlim
))
600 # Raising the soft limit is sufficient.
601 resource
.setrlimit(resource
.RLIMIT_NOFILE
, (newlim
, hardlim
))
604 sys
.exit("NOFILE rlimit could not be raised to {0}".format(newlim
))
606 for groupid
in groupids
:
611 match
= re
.match(r
'(.*)\((.*)\)', name
)
613 tracepoint
, sub
= match
.groups()
614 tracefilter
= ('%s==%d\0' %
615 (self
.filters
[tracepoint
][0],
616 self
.filters
[tracepoint
][1][sub
]))
618 # From perf_event_open(2):
619 # pid > 0 and cpu == -1
620 # This measures the specified process/thread on any CPU.
622 # pid == -1 and cpu >= 0
623 # This measures all processes/threads on the specified CPU.
624 trace_cpu
= groupid
if self
._pid
== 0 else -1
625 trace_pid
= int(groupid
) if self
._pid
!= 0 else -1
627 group
.add_event(Event(name
=name
,
631 trace_point
=tracepoint
,
632 trace_filter
=tracefilter
))
634 self
.group_leaders
.append(group
)
641 def fields(self
, fields
):
642 """Enables/disables the (un)wanted events"""
643 self
._fields
= fields
644 for group
in self
.group_leaders
:
645 for index
, event
in enumerate(group
.events
):
646 if event
.name
in fields
:
650 # Do not disable the group leader.
651 # It would disable all of its events.
661 """Changes the monitored pid by setting new traces."""
663 # The garbage collector will get rid of all Event/Group
664 # objects and open files after removing the references.
665 self
.group_leaders
= []
667 self
.fields
= self
._fields
669 def read(self
, by_guest
=0):
670 """Returns 'event name: current value' for all enabled events."""
671 ret
= defaultdict(int)
672 for group
in self
.group_leaders
:
673 for name
, val
in group
.read().items():
674 if name
in self
._fields
:
679 """Reset all field counters"""
680 for group
in self
.group_leaders
:
681 for event
in group
.events
:
685 class DebugfsProvider(Provider
):
686 """Provides data from the files that KVM creates in the kvm debugfs
688 def __init__(self
, pid
, fields_filter
, include_past
):
689 self
.update_fields(fields_filter
)
697 def get_available_fields(self
):
698 """"Returns a list of available fields.
700 The fields are all available KVM debugfs files
703 return self
.walkdir(PATH_DEBUGFS_KVM
)[2]
705 def update_fields(self
, fields_filter
):
706 """Refresh fields, applying fields_filter"""
707 self
._fields
= [field
for field
in self
.get_available_fields()
708 if self
.is_field_wanted(fields_filter
, field
)]
715 def fields(self
, fields
):
716 self
._fields
= fields
727 vms
= self
.walkdir(PATH_DEBUGFS_KVM
)[1]
731 self
.paths
= filter(lambda x
: "{}-".format(pid
) in x
, vms
)
738 def read(self
, reset
=0, by_guest
=0):
739 """Returns a dict with format:'file name / field -> current value'.
743 1 reset field counts to 0
744 2 restore the original field counts
749 # If no debugfs filtering support is available, then don't read.
756 for entry
in os
.walk(PATH_DEBUGFS_KVM
):
760 for field
in self
._fields
:
761 value
= self
.read_field(field
, path
)
764 self
._baseline
[key
] = value
766 self
._baseline
[key
] = 0
767 if self
._baseline
.get(key
, -1) == -1:
768 self
._baseline
[key
] = value
769 increment
= (results
.get(field
, 0) + value
-
770 self
._baseline
.get(key
, 0))
772 pid
= key
.split('-')[0]
774 results
[pid
] += increment
776 results
[pid
] = increment
778 results
[field
] = increment
782 def read_field(self
, field
, path
):
783 """Returns the value of a single field from a specific VM."""
785 return int(open(os
.path
.join(PATH_DEBUGFS_KVM
,
793 """Reset field counters"""
798 """Reset field counters"""
803 EventStat
= namedtuple('EventStat', ['value', 'delta'])
807 """Manages the data providers and the data they provide.
809 It is used to set filters on the provider's data and collect all
813 def __init__(self
, options
):
814 self
.providers
= self
.get_providers(options
)
815 self
._pid
_filter
= options
.pid
816 self
._fields
_filter
= options
.fields
820 def get_providers(options
):
821 """Returns a list of data providers depending on the passed options."""
825 providers
.append(DebugfsProvider(options
.pid
, options
.fields
,
826 options
.dbgfs_include_past
))
827 if options
.tracepoints
or not providers
:
828 providers
.append(TracepointProvider(options
.pid
, options
.fields
))
832 def update_provider_filters(self
):
833 """Propagates fields filters to providers."""
834 # As we reset the counters when updating the fields we can
835 # also clear the cache of old values.
837 for provider
in self
.providers
:
838 provider
.update_fields(self
._fields
_filter
)
842 for provider
in self
.providers
:
846 def fields_filter(self
):
847 return self
._fields
_filter
849 @fields_filter.setter
850 def fields_filter(self
, fields_filter
):
851 if fields_filter
!= self
._fields
_filter
:
852 self
._fields
_filter
= fields_filter
853 self
.update_provider_filters()
856 def pid_filter(self
):
857 return self
._pid
_filter
860 def pid_filter(self
, pid
):
861 if pid
!= self
._pid
_filter
:
862 self
._pid
_filter
= pid
864 for provider
in self
.providers
:
865 provider
.pid
= self
._pid
_filter
867 def get(self
, by_guest
=0):
868 """Returns a dict with field -> (value, delta to last value) of all
870 for provider
in self
.providers
:
871 new
= provider
.read(by_guest
=by_guest
)
872 for key
in new
if by_guest
else provider
.fields
:
873 oldval
= self
.values
.get(key
, EventStat(0, 0)).value
874 newval
= new
.get(key
, 0)
875 newdelta
= newval
- oldval
876 self
.values
[key
] = EventStat(newval
, newdelta
)
879 def toggle_display_guests(self
, to_pid
):
880 """Toggle between collection of stats by individual event and by
883 Events reported by DebugfsProvider change when switching to/from
884 reading by guest values. Hence we have to remove the excess event
885 names from self.values.
888 if any(isinstance(ins
, TracepointProvider
) for ins
in self
.providers
):
891 for provider
in self
.providers
:
892 if isinstance(provider
, DebugfsProvider
):
893 for key
in provider
.fields
:
894 if key
in self
.values
.keys():
897 oldvals
= self
.values
.copy()
901 # Update oldval (see get())
906 MAX_GUEST_NAME_LEN
= 48
908 DEFAULT_REGEX
= r
'^[^\(]*$'
913 """Instruments curses to draw a nice text ui."""
914 def __init__(self
, stats
):
917 self
._delay
_initial
= 0.25
918 self
._delay
_regular
= DELAY_DEFAULT
919 self
._sorting
= SORT_DEFAULT
920 self
._display
_guests
= 0
923 """Initialises curses for later use. Based on curses.wrapper
924 implementation from the Python standard library."""
925 self
.screen
= curses
.initscr()
929 # The try/catch works around a minor bit of
930 # over-conscientiousness in the curses module, the error
931 # return from C start_color() is ignorable.
937 # Hide cursor in extra statement as some monochrome terminals
938 # might support hiding but not colors.
944 curses
.use_default_colors()
947 def __exit__(self
, *exception
):
948 """Resets the terminal to its normal state. Based on curses.wrapper
949 implementation from the Python standard library."""
951 self
.screen
.keypad(0)
957 def get_all_gnames():
958 """Returns a list of (pid, gname) tuples of all running guests"""
961 child
= subprocess
.Popen(['ps', '-A', '--format', 'pid,args'],
962 stdout
=subprocess
.PIPE
)
965 for line
in child
.stdout
:
966 line
= line
.decode(ENCODING
).lstrip().split(' ', 1)
967 # perform a sanity check before calling the more expensive
968 # function to possibly extract the guest name
969 if ' -name ' in line
[1]:
970 res
.append((line
[0], Tui
.get_gname_from_pid(line
[0])))
975 def print_all_gnames(self
, row
):
976 """Print a list of all running guests along with their pids."""
977 self
.screen
.addstr(row
, 2, '%8s %-60s' %
978 ('Pid', 'Guest Name (fuzzy list, might be '
983 for line
in self
.get_all_gnames():
984 self
.screen
.addstr(row
, 2, '%8s %-60s' % (line
[0], line
[1]))
986 if row
>= self
.screen
.getmaxyx()[0]:
989 self
.screen
.addstr(row
+ 1, 2, 'Not available')
992 def get_pid_from_gname(gname
):
993 """Fuzzy function to convert guest name to QEMU process pid.
995 Returns a list of potential pids, can be empty if no match found.
996 Throws an exception on processing errors.
1000 for line
in Tui
.get_all_gnames():
1001 if gname
== line
[1]:
1002 pids
.append(int(line
[0]))
1007 def get_gname_from_pid(pid
):
1008 """Returns the guest name for a QEMU process pid.
1010 Extracts the guest name from the QEMU comma line by processing the
1011 '-name' option. Will also handle names specified out of sequence.
1016 line
= open('/proc/{}/cmdline'
1017 .format(pid
), 'r').read().split('\0')
1018 parms
= line
[line
.index('-name') + 1].split(',')
1020 # commas are escaped (i.e. ',,'), hence e.g. 'foo,bar' results
1021 # in # ['foo', '', 'bar'], which we revert here
1022 idx
= parms
.index('')
1023 parms
[idx
- 1] += ',' + parms
[idx
+ 1]
1024 del parms
[idx
:idx
+2]
1025 # the '-name' switch allows for two ways to specify the guest name,
1026 # where the plain name overrides the name specified via 'guest='
1031 if arg
[:6] == 'guest=':
1033 except (ValueError, IOError, IndexError):
1038 def update_drilldown(self
):
1039 """Sets or removes a filter that only allows fields without braces."""
1040 if not self
.stats
.fields_filter
:
1041 self
.stats
.fields_filter
= DEFAULT_REGEX
1043 elif self
.stats
.fields_filter
== DEFAULT_REGEX
:
1044 self
.stats
.fields_filter
= None
1046 def update_pid(self
, pid
):
1047 """Propagates pid selection to stats object."""
1048 self
.stats
.pid_filter
= pid
1050 def refresh_header(self
, pid
=None):
1051 """Refreshes the header."""
1053 pid
= self
.stats
.pid_filter
1055 gname
= self
.get_gname_from_pid(pid
)
1057 gname
= ('({})'.format(gname
[:MAX_GUEST_NAME_LEN
] + '...'
1058 if len(gname
) > MAX_GUEST_NAME_LEN
1061 self
.screen
.addstr(0, 0, 'kvm statistics - pid {0} {1}'
1062 .format(pid
, gname
), curses
.A_BOLD
)
1064 self
.screen
.addstr(0, 0, 'kvm statistics - summary', curses
.A_BOLD
)
1065 if self
.stats
.fields_filter
and self
.stats
.fields_filter \
1067 regex
= self
.stats
.fields_filter
1068 if len(regex
) > MAX_REGEX_LEN
:
1069 regex
= regex
[:MAX_REGEX_LEN
] + '...'
1070 self
.screen
.addstr(1, 17, 'regex filter: {0}'.format(regex
))
1071 if self
._display
_guests
:
1072 col_name
= 'Guest Name'
1075 self
.screen
.addstr(2, 1, '%-40s %10s%7s %8s' %
1076 (col_name
, 'Total', '%Total', 'CurAvg/s'),
1078 self
.screen
.addstr(4, 1, 'Collecting data...')
1079 self
.screen
.refresh()
1081 def refresh_body(self
, sleeptime
):
1083 self
.screen
.move(row
, 0)
1084 self
.screen
.clrtobot()
1085 stats
= self
.stats
.get(self
._display
_guests
)
1087 for key
, values
in stats
.items():
1088 if key
.find('(') == -1:
1089 total
+= values
.value
1091 if self
._sorting
== SORT_DEFAULT
:
1092 def sortkey((_k
, v
)):
1093 # sort by (delta value, overall value)
1094 return (v
.delta
, v
.value
)
1096 def sortkey((_k
, v
)):
1097 # sort by overall value
1101 for key
, values
in sorted(stats
.items(), key
=sortkey
, reverse
=True):
1102 if row
>= self
.screen
.getmaxyx()[0] - 1:
1104 if not values
.value
and not values
.delta
:
1106 if values
.value
is not None:
1107 cur
= int(round(values
.delta
/ sleeptime
)) if values
.delta
else ''
1108 if self
._display
_guests
:
1109 key
= self
.get_gname_from_pid(key
)
1110 self
.screen
.addstr(row
, 1, '%-40s %10d%7.1f %8s' %
1111 (key
, values
.value
, values
.value
* 100 / total
,
1113 if cur
!= '' and key
.find('(') == -1:
1117 self
.screen
.addstr(4, 1, 'No matching events reported yet')
1119 self
.screen
.addstr(row
, 1, '%-40s %10d %8s' %
1120 ('Total', total
, tavg
if tavg
else ''),
1122 self
.screen
.refresh()
1124 def show_msg(self
, text
):
1125 """Display message centered text and exit on key press"""
1126 hint
= 'Press any key to continue'
1129 (x
, term_width
) = self
.screen
.getmaxyx()
1132 start
= (term_width
- len(line
)) / 2
1133 self
.screen
.addstr(row
, start
, line
)
1135 self
.screen
.addstr(row
+ 1, (term_width
- len(hint
)) / 2, hint
,
1137 self
.screen
.getkey()
1139 def show_help_interactive(self
):
1140 """Display help with list of interactive commands"""
1141 msg
= (' b toggle events by guests (debugfs only, honors'
1144 ' f filter by regular expression',
1145 ' g filter by guest name',
1146 ' h display interactive commands reference',
1147 ' o toggle sorting order (Total vs CurAvg/s)',
1151 ' s set update interval',
1152 ' x toggle reporting of stats for individual child trace'
1154 'Any other key refreshes statistics immediately')
1157 self
.screen
.addstr(0, 0, "Interactive commands reference",
1159 self
.screen
.addstr(2, 0, "Press any key to exit", curses
.A_STANDOUT
)
1162 self
.screen
.addstr(row
, 0, line
)
1164 self
.screen
.getkey()
1165 self
.refresh_header()
1167 def show_filter_selection(self
):
1168 """Draws filter selection mask.
1170 Asks for a valid regex and sets the fields filter accordingly.
1175 self
.screen
.addstr(0, 0,
1176 "Show statistics for events matching a regex.",
1178 self
.screen
.addstr(2, 0,
1179 "Current regex: {0}"
1180 .format(self
.stats
.fields_filter
))
1181 self
.screen
.addstr(3, 0, "New regex: ")
1183 regex
= self
.screen
.getstr().decode(ENCODING
)
1186 self
.stats
.fields_filter
= DEFAULT_REGEX
1187 self
.refresh_header()
1191 self
.stats
.fields_filter
= regex
1192 self
.refresh_header()
1197 def show_vm_selection_by_pid(self
):
1198 """Draws PID selection mask.
1200 Asks for a pid until a valid pid or 0 has been entered.
1206 self
.screen
.addstr(0, 0,
1207 'Show statistics for specific pid.',
1209 self
.screen
.addstr(1, 0,
1210 'This might limit the shown data to the trace '
1212 self
.screen
.addstr(5, 0, msg
)
1213 self
.print_all_gnames(7)
1216 self
.screen
.addstr(3, 0, "Pid [0 or pid]: ")
1217 pid
= self
.screen
.getstr().decode(ENCODING
)
1223 if pid
!= 0 and not os
.path
.isdir(os
.path
.join('/proc/',
1225 msg
= '"' + str(pid
) + '": Not a running process'
1229 self
.refresh_header(pid
)
1230 self
.update_pid(pid
)
1233 msg
= '"' + str(pid
) + '": Not a valid pid'
1235 def show_set_update_interval(self
):
1236 """Draws update interval selection mask."""
1240 self
.screen
.addstr(0, 0, 'Set update interval (defaults to %fs).' %
1241 DELAY_DEFAULT
, curses
.A_BOLD
)
1242 self
.screen
.addstr(4, 0, msg
)
1243 self
.screen
.addstr(2, 0, 'Change delay from %.1fs to ' %
1244 self
._delay
_regular
)
1246 val
= self
.screen
.getstr().decode(ENCODING
)
1253 msg
= '"' + str(val
) + '": Value must be >=0.1'
1256 msg
= '"' + str(val
) + '": Value must be <=25.5'
1259 delay
= DELAY_DEFAULT
1260 self
._delay
_regular
= delay
1264 msg
= '"' + str(val
) + '": Invalid value'
1265 self
.refresh_header()
1267 def show_vm_selection_by_guest_name(self
):
1268 """Draws guest selection mask.
1270 Asks for a guest name until a valid guest name or '' is entered.
1276 self
.screen
.addstr(0, 0,
1277 'Show statistics for specific guest.',
1279 self
.screen
.addstr(1, 0,
1280 'This might limit the shown data to the trace '
1282 self
.screen
.addstr(5, 0, msg
)
1283 self
.print_all_gnames(7)
1285 self
.screen
.addstr(3, 0, "Guest [ENTER or guest]: ")
1286 gname
= self
.screen
.getstr().decode(ENCODING
)
1290 self
.refresh_header(0)
1296 pids
= self
.get_pid_from_gname(gname
)
1298 msg
= '"' + gname
+ '": Internal error while searching, ' \
1299 'use pid filter instead'
1302 msg
= '"' + gname
+ '": Not an active guest'
1305 msg
= '"' + gname
+ '": Multiple matches found, use pid ' \
1308 self
.refresh_header(pids
[0])
1309 self
.update_pid(pids
[0])
1312 def show_stats(self
):
1313 """Refreshes the screen and processes user input."""
1314 sleeptime
= self
._delay
_initial
1315 self
.refresh_header()
1316 start
= 0.0 # result based on init value never appears on screen
1318 self
.refresh_body(time
.time() - start
)
1319 curses
.halfdelay(int(sleeptime
* 10))
1321 sleeptime
= self
._delay
_regular
1323 char
= self
.screen
.getkey()
1325 self
._display
_guests
= not self
._display
_guests
1326 if self
.stats
.toggle_display_guests(self
._display
_guests
):
1327 self
.show_msg(['Command not available with tracepoints'
1328 ' enabled', 'Restart with debugfs only '
1329 '(see option \'-d\') and try again!'])
1330 self
._display
_guests
= not self
._display
_guests
1331 self
.refresh_header()
1333 self
.stats
.fields_filter
= DEFAULT_REGEX
1334 self
.refresh_header(0)
1338 self
.show_filter_selection()
1340 sleeptime
= self
._delay
_initial
1343 self
.show_vm_selection_by_guest_name()
1345 sleeptime
= self
._delay
_initial
1347 self
.show_help_interactive()
1349 self
._sorting
= not self
._sorting
1352 self
.show_vm_selection_by_pid()
1354 sleeptime
= self
._delay
_initial
1361 self
.show_set_update_interval()
1363 sleeptime
= self
._delay
_initial
1365 self
.update_drilldown()
1366 # prevents display of current values on next refresh
1367 self
.stats
.get(self
._display
_guests
)
1368 except KeyboardInterrupt:
1370 except curses
.error
:
1375 """Prints statistics in a key, value format."""
1380 for key
, values
in sorted(s
.items()):
1381 print('%-42s%10d%10d' % (key
, values
.value
, values
.delta
))
1382 except KeyboardInterrupt:
1387 """Prints statistics as reiterating key block, multiple value blocks."""
1388 keys
= sorted(stats
.get().keys())
1398 print(' %9d' % s
[key
].delta
, end
=' ')
1405 if line
% banner_repeat
== 0:
1409 except KeyboardInterrupt:
1414 """Returns processed program arguments."""
1415 description_text
= """
1416 This script displays various statistics about VMs running under KVM.
1417 The statistics are gathered from the KVM debugfs entries and / or the
1418 currently available perf traces.
1420 The monitoring takes additional cpu cycles and might affect the VM's
1428 - /proc/sys/kernel/perf_event_paranoid < 1 if user has no
1429 CAP_SYS_ADMIN and perf events are used.
1430 - CAP_SYS_RESOURCE if the hard limit is not high enough to allow
1431 the large number of files that are possibly opened.
1433 Interactive Commands:
1434 b toggle events by guests (debugfs only, honors filters)
1436 f filter by regular expression
1437 g filter by guest name
1438 h display interactive commands reference
1439 o toggle sorting order (Total vs CurAvg/s)
1443 s set update interval
1444 x toggle reporting of stats for individual child trace events
1445 Press any other key to refresh statistics immediately.
1446 """ % (PATH_DEBUGFS_KVM
, PATH_DEBUGFS_TRACING
)
1448 class PlainHelpFormatter(optparse
.IndentedHelpFormatter
):
1449 def format_description(self
, description
):
1451 return description
+ "\n"
1455 def cb_guest_to_pid(option
, opt
, val
, parser
):
1457 pids
= Tui
.get_pid_from_gname(val
)
1459 sys
.exit('Error while searching for guest "{}". Use "-p" to '
1460 'specify a pid instead?'.format(val
))
1462 sys
.exit('Error: No guest by the name "{}" found'.format(val
))
1464 sys
.exit('Error: Multiple processes found (pids: {}). Use "-p" '
1465 'to specify the desired pid'.format(" ".join(pids
)))
1466 parser
.values
.pid
= pids
[0]
1468 optparser
= optparse
.OptionParser(description
=description_text
,
1469 formatter
=PlainHelpFormatter())
1470 optparser
.add_option('-1', '--once', '--batch',
1471 action
='store_true',
1474 help='run in batch mode for one second',
1476 optparser
.add_option('-i', '--debugfs-include-past',
1477 action
='store_true',
1479 dest
='dbgfs_include_past',
1480 help='include all available data on past events for '
1483 optparser
.add_option('-l', '--log',
1484 action
='store_true',
1487 help='run in logging mode (like vmstat)',
1489 optparser
.add_option('-t', '--tracepoints',
1490 action
='store_true',
1493 help='retrieve statistics from tracepoints',
1495 optparser
.add_option('-d', '--debugfs',
1496 action
='store_true',
1499 help='retrieve statistics from debugfs',
1501 optparser
.add_option('-f', '--fields',
1503 default
=DEFAULT_REGEX
,
1505 help='''fields to display (regex)
1506 "-f help" for a list of available events''',
1508 optparser
.add_option('-p', '--pid',
1513 help='restrict statistics to pid',
1515 optparser
.add_option('-g', '--guest',
1520 help='restrict statistics to guest by name',
1521 callback
=cb_guest_to_pid
,
1523 options
, unkn
= optparser
.parse_args(sys
.argv
)
1525 sys
.exit('Error: Extra argument(s): ' + ' '.join(unkn
[1:]))
1527 # verify that we were passed a valid regex up front
1528 re
.compile(options
.fields
)
1530 sys
.exit('Error: "' + options
.fields
+ '" is not a valid regular '
1536 def check_access(options
):
1537 """Exits if the current user can't access all needed directories."""
1538 if not os
.path
.exists('/sys/kernel/debug'):
1539 sys
.stderr
.write('Please enable CONFIG_DEBUG_FS in your kernel.')
1542 if not os
.path
.exists(PATH_DEBUGFS_KVM
):
1543 sys
.stderr
.write("Please make sure, that debugfs is mounted and "
1544 "readable by the current user:\n"
1545 "('mount -t debugfs debugfs /sys/kernel/debug')\n"
1546 "Also ensure, that the kvm modules are loaded.\n")
1549 if not os
.path
.exists(PATH_DEBUGFS_TRACING
) and (options
.tracepoints
or
1550 not options
.debugfs
):
1551 sys
.stderr
.write("Please enable CONFIG_TRACING in your kernel "
1552 "when using the option -t (default).\n"
1553 "If it is enabled, make {0} readable by the "
1555 .format(PATH_DEBUGFS_TRACING
))
1556 if options
.tracepoints
:
1559 sys
.stderr
.write("Falling back to debugfs statistics!\n")
1560 options
.debugfs
= True
1567 options
= get_options()
1568 options
= check_access(options
)
1570 if (options
.pid
> 0 and
1571 not os
.path
.isdir(os
.path
.join('/proc/',
1572 str(options
.pid
)))):
1573 sys
.stderr
.write('Did you use a (unsupported) tid instead of a pid?\n')
1574 sys
.exit('Specified pid does not exist.')
1576 stats
= Stats(options
)
1578 if options
.fields
== 'help':
1579 stats
.fields_filter
= None
1581 for key
in stats
.get().keys():
1582 event_list
.append(key
.split('(', 1)[0])
1583 sys
.stdout
.write(' ' + '\n '.join(sorted(set(event_list
))) + '\n')
1588 elif not options
.once
:
1589 with
Tui(stats
) as tui
:
1594 if __name__
== "__main__":