]> git.proxmox.com Git - mirror_ubuntu-focal-kernel.git/blame - tools/kvm/kvm_stat/kvm_stat
Merge remote-tracking branches 'asoc/topic/tas6424', 'asoc/topic/tfa9879', 'asoc...
[mirror_ubuntu-focal-kernel.git] / tools / kvm / kvm_stat / kvm_stat
CommitLineData
f9bc9e65
JF
1#!/usr/bin/python
2#
3# top-like utility for displaying kvm statistics
4#
5# Copyright 2006-2008 Qumranet Technologies
6# Copyright 2008-2011 Red Hat, Inc.
7#
8# Authors:
9# Avi Kivity <avi@redhat.com>
10#
11# This work is licensed under the terms of the GNU GPL, version 2. See
12# the COPYING file in the top-level directory.
fabc7128
JF
13"""The kvm_stat module outputs statistics about running KVM VMs
14
15Three 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
19
20The data is sampled from the KVM's debugfs entries and its perf events.
21"""
9cc5fbbb 22from __future__ import print_function
f9bc9e65
JF
23
24import curses
25import sys
9cc5fbbb 26import locale
f9bc9e65
JF
27import os
28import time
29import optparse
30import ctypes
31import fcntl
32import resource
33import struct
34import re
f9ff1087 35import subprocess
f9bc9e65 36from collections import defaultdict
f9bc9e65
JF
37
38VMX_EXIT_REASONS = {
39 'EXCEPTION_NMI': 0,
40 'EXTERNAL_INTERRUPT': 1,
41 'TRIPLE_FAULT': 2,
42 'PENDING_INTERRUPT': 7,
43 'NMI_WINDOW': 8,
44 'TASK_SWITCH': 9,
45 'CPUID': 10,
46 'HLT': 12,
47 'INVLPG': 14,
48 'RDPMC': 15,
49 'RDTSC': 16,
50 'VMCALL': 18,
51 'VMCLEAR': 19,
52 'VMLAUNCH': 20,
53 'VMPTRLD': 21,
54 'VMPTRST': 22,
55 'VMREAD': 23,
56 'VMRESUME': 24,
57 'VMWRITE': 25,
58 'VMOFF': 26,
59 'VMON': 27,
60 'CR_ACCESS': 28,
61 'DR_ACCESS': 29,
62 'IO_INSTRUCTION': 30,
63 'MSR_READ': 31,
64 'MSR_WRITE': 32,
65 'INVALID_STATE': 33,
66 'MWAIT_INSTRUCTION': 36,
67 'MONITOR_INSTRUCTION': 39,
68 'PAUSE_INSTRUCTION': 40,
69 'MCE_DURING_VMENTRY': 41,
70 'TPR_BELOW_THRESHOLD': 43,
71 'APIC_ACCESS': 44,
72 'EPT_VIOLATION': 48,
73 'EPT_MISCONFIG': 49,
74 'WBINVD': 54,
75 'XSETBV': 55,
76 'APIC_WRITE': 56,
77 'INVPCID': 58,
78}
79
80SVM_EXIT_REASONS = {
81 'READ_CR0': 0x000,
82 'READ_CR3': 0x003,
83 'READ_CR4': 0x004,
84 'READ_CR8': 0x008,
85 'WRITE_CR0': 0x010,
86 'WRITE_CR3': 0x013,
87 'WRITE_CR4': 0x014,
88 'WRITE_CR8': 0x018,
89 'READ_DR0': 0x020,
90 'READ_DR1': 0x021,
91 'READ_DR2': 0x022,
92 'READ_DR3': 0x023,
93 'READ_DR4': 0x024,
94 'READ_DR5': 0x025,
95 'READ_DR6': 0x026,
96 'READ_DR7': 0x027,
97 'WRITE_DR0': 0x030,
98 'WRITE_DR1': 0x031,
99 'WRITE_DR2': 0x032,
100 'WRITE_DR3': 0x033,
101 'WRITE_DR4': 0x034,
102 'WRITE_DR5': 0x035,
103 'WRITE_DR6': 0x036,
104 'WRITE_DR7': 0x037,
105 'EXCP_BASE': 0x040,
106 'INTR': 0x060,
107 'NMI': 0x061,
108 'SMI': 0x062,
109 'INIT': 0x063,
110 'VINTR': 0x064,
111 'CR0_SEL_WRITE': 0x065,
112 'IDTR_READ': 0x066,
113 'GDTR_READ': 0x067,
114 'LDTR_READ': 0x068,
115 'TR_READ': 0x069,
116 'IDTR_WRITE': 0x06a,
117 'GDTR_WRITE': 0x06b,
118 'LDTR_WRITE': 0x06c,
119 'TR_WRITE': 0x06d,
120 'RDTSC': 0x06e,
121 'RDPMC': 0x06f,
122 'PUSHF': 0x070,
123 'POPF': 0x071,
124 'CPUID': 0x072,
125 'RSM': 0x073,
126 'IRET': 0x074,
127 'SWINT': 0x075,
128 'INVD': 0x076,
129 'PAUSE': 0x077,
130 'HLT': 0x078,
131 'INVLPG': 0x079,
132 'INVLPGA': 0x07a,
133 'IOIO': 0x07b,
134 'MSR': 0x07c,
135 'TASK_SWITCH': 0x07d,
136 'FERR_FREEZE': 0x07e,
137 'SHUTDOWN': 0x07f,
138 'VMRUN': 0x080,
139 'VMMCALL': 0x081,
140 'VMLOAD': 0x082,
141 'VMSAVE': 0x083,
142 'STGI': 0x084,
143 'CLGI': 0x085,
144 'SKINIT': 0x086,
145 'RDTSCP': 0x087,
146 'ICEBP': 0x088,
147 'WBINVD': 0x089,
148 'MONITOR': 0x08a,
149 'MWAIT': 0x08b,
150 'MWAIT_COND': 0x08c,
151 'XSETBV': 0x08d,
152 'NPF': 0x400,
153}
154
155# EC definition of HSR (from arch/arm64/include/asm/kvm_arm.h)
156AARCH64_EXIT_REASONS = {
157 'UNKNOWN': 0x00,
158 'WFI': 0x01,
159 'CP15_32': 0x03,
160 'CP15_64': 0x04,
161 'CP14_MR': 0x05,
162 'CP14_LS': 0x06,
163 'FP_ASIMD': 0x07,
164 'CP10_ID': 0x08,
165 'CP14_64': 0x0C,
166 'ILL_ISS': 0x0E,
167 'SVC32': 0x11,
168 'HVC32': 0x12,
169 'SMC32': 0x13,
170 'SVC64': 0x15,
171 'HVC64': 0x16,
172 'SMC64': 0x17,
173 'SYS64': 0x18,
174 'IABT': 0x20,
175 'IABT_HYP': 0x21,
176 'PC_ALIGN': 0x22,
177 'DABT': 0x24,
178 'DABT_HYP': 0x25,
179 'SP_ALIGN': 0x26,
180 'FP_EXC32': 0x28,
181 'FP_EXC64': 0x2C,
182 'SERROR': 0x2F,
183 'BREAKPT': 0x30,
184 'BREAKPT_HYP': 0x31,
185 'SOFTSTP': 0x32,
186 'SOFTSTP_HYP': 0x33,
187 'WATCHPT': 0x34,
188 'WATCHPT_HYP': 0x35,
189 'BKPT32': 0x38,
190 'VECTOR32': 0x3A,
191 'BRK64': 0x3C,
192}
193
194# From include/uapi/linux/kvm.h, KVM_EXIT_xxx
195USERSPACE_EXIT_REASONS = {
196 'UNKNOWN': 0,
197 'EXCEPTION': 1,
198 'IO': 2,
199 'HYPERCALL': 3,
200 'DEBUG': 4,
201 'HLT': 5,
202 'MMIO': 6,
203 'IRQ_WINDOW_OPEN': 7,
204 'SHUTDOWN': 8,
205 'FAIL_ENTRY': 9,
206 'INTR': 10,
207 'SET_TPR': 11,
208 'TPR_ACCESS': 12,
209 'S390_SIEIC': 13,
210 'S390_RESET': 14,
211 'DCR': 15,
212 'NMI': 16,
213 'INTERNAL_ERROR': 17,
214 'OSI': 18,
215 'PAPR_HCALL': 19,
216 'S390_UCONTROL': 20,
217 'WATCHDOG': 21,
218 'S390_TSCH': 22,
219 'EPR': 23,
220 'SYSTEM_EVENT': 24,
221}
222
223IOCTL_NUMBERS = {
224 'SET_FILTER': 0x40082406,
225 'ENABLE': 0x00002400,
226 'DISABLE': 0x00002401,
227 'RESET': 0x00002403,
228}
229
9cc5fbbb
JC
230ENCODING = locale.getpreferredencoding(False)
231
692c7f6d 232
f9bc9e65 233class Arch(object):
fabc7128
JF
234 """Encapsulates global architecture specific data.
235
236 Contains the performance event open syscall and ioctl numbers, as
237 well as the VM exit reasons for the architecture it runs on.
f9bc9e65
JF
238
239 """
240 @staticmethod
241 def get_arch():
242 machine = os.uname()[4]
243
244 if machine.startswith('ppc'):
245 return ArchPPC()
246 elif machine.startswith('aarch64'):
247 return ArchA64()
248 elif machine.startswith('s390'):
249 return ArchS390()
250 else:
251 # X86_64
252 for line in open('/proc/cpuinfo'):
253 if not line.startswith('flags'):
254 continue
255
256 flags = line.split()
257 if 'vmx' in flags:
258 return ArchX86(VMX_EXIT_REASONS)
259 if 'svm' in flags:
260 return ArchX86(SVM_EXIT_REASONS)
261 return
262
692c7f6d 263
f9bc9e65
JF
264class ArchX86(Arch):
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
269
692c7f6d 270
f9bc9e65
JF
271class ArchPPC(Arch):
272 def __init__(self):
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
c7d4fb5a 277 self.ioctl_numbers['RESET'] = 0x20002403
f9bc9e65
JF
278
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
c7d4fb5a 283 self.exit_reasons = {}
f9bc9e65 284
692c7f6d 285
f9bc9e65
JF
286class ArchA64(Arch):
287 def __init__(self):
288 self.sc_perf_evt_open = 241
289 self.ioctl_numbers = IOCTL_NUMBERS
290 self.exit_reasons = AARCH64_EXIT_REASONS
291
692c7f6d 292
f9bc9e65
JF
293class ArchS390(Arch):
294 def __init__(self):
295 self.sc_perf_evt_open = 331
296 self.ioctl_numbers = IOCTL_NUMBERS
297 self.exit_reasons = None
298
299ARCH = Arch.get_arch()
300
301
f9bc9e65 302class perf_event_attr(ctypes.Structure):
fabc7128
JF
303 """Struct that holds the necessary data to set up a trace event.
304
305 For an extensive explanation see perf_event_open(2) and
306 include/uapi/linux/perf_event.h, struct perf_event_attr
307
308 All fields that are not initialized in the constructor are 0.
309
310 """
f9bc9e65
JF
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),
322 ]
323
324 def __init__(self):
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
329
692c7f6d 330
f9bc9e65
JF
331PERF_TYPE_TRACEPOINT = 2
332PERF_FORMAT_GROUP = 1 << 3
333
334PATH_DEBUGFS_TRACING = '/sys/kernel/debug/tracing'
335PATH_DEBUGFS_KVM = '/sys/kernel/debug/kvm'
336
692c7f6d 337
f9bc9e65 338class Group(object):
fabc7128
JF
339 """Represents a perf event group."""
340
f9bc9e65
JF
341 def __init__(self):
342 self.events = []
343
344 def add_event(self, event):
345 self.events.append(event)
346
347 def read(self):
fabc7128
JF
348 """Returns a dict with 'event name: value' for all events in the
349 group.
350
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
353 details.
354
355 Read format for the used event configuration is:
356 struct read_format {
357 u64 nr; /* The number of events */
358 struct {
359 u64 value; /* The value of the event */
360 } values[nr];
361 };
362
363 """
f9bc9e65
JF
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))))
369
692c7f6d 370
f9bc9e65 371class Event(object):
fabc7128 372 """Represents a performance event and manages its life cycle."""
f0cf040f
JF
373 def __init__(self, name, group, trace_cpu, trace_pid, trace_point,
374 trace_filter, trace_set='kvm'):
099a2dfc
SR
375 self.libc = ctypes.CDLL('libc.so.6', use_errno=True)
376 self.syscall = self.libc.syscall
f9bc9e65
JF
377 self.name = name
378 self.fd = None
f0cf040f
JF
379 self.setup_event(group, trace_cpu, trace_pid, trace_point,
380 trace_filter, trace_set)
381
382 def __del__(self):
fabc7128
JF
383 """Closes the event's file descriptor.
384
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.
388
389 """
f0cf040f
JF
390 if self.fd:
391 os.close(self.fd)
f9bc9e65 392
099a2dfc
SR
393 def perf_event_open(self, attr, pid, cpu, group_fd, flags):
394 """Wrapper for the sys_perf_evt_open() syscall.
395
396 Used to set up performance events, returns a file descriptor or -1
397 on error.
398
399 Attributes are:
400 - syscall number
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.
405 - flags
406
407 """
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))
411
f9bc9e65 412 def setup_event_attribute(self, trace_set, trace_point):
fabc7128
JF
413 """Returns an initialized ctype perf_event_attr struct."""
414
f9bc9e65
JF
415 id_path = os.path.join(PATH_DEBUGFS_TRACING, 'events', trace_set,
416 trace_point, 'id')
417
418 event_attr = perf_event_attr()
419 event_attr.config = int(open(id_path).read())
420 return event_attr
421
f0cf040f
JF
422 def setup_event(self, group, trace_cpu, trace_pid, trace_point,
423 trace_filter, trace_set):
fabc7128
JF
424 """Sets up the perf event in Linux.
425
426 Issues the syscall to register the event in the kernel and
427 then sets the optional filter.
428
429 """
430
f9bc9e65
JF
431 event_attr = self.setup_event_attribute(trace_set, trace_point)
432
fabc7128 433 # First event will be group leader.
f9bc9e65 434 group_leader = -1
fabc7128
JF
435
436 # All others have to pass the leader's descriptor instead.
f9bc9e65
JF
437 if group.events:
438 group_leader = group.events[0].fd
439
099a2dfc
SR
440 fd = self.perf_event_open(event_attr, trace_pid,
441 trace_cpu, group_leader, 0)
f9bc9e65
JF
442 if fd == -1:
443 err = ctypes.get_errno()
444 raise OSError(err, os.strerror(err),
445 'while calling sys_perf_event_open().')
446
447 if trace_filter:
448 fcntl.ioctl(fd, ARCH.ioctl_numbers['SET_FILTER'],
449 trace_filter)
450
451 self.fd = fd
452
453 def enable(self):
fabc7128
JF
454 """Enables the trace event in the kernel.
455
456 Enabling the group leader makes reading counters from it and the
457 events under it possible.
458
459 """
f9bc9e65
JF
460 fcntl.ioctl(self.fd, ARCH.ioctl_numbers['ENABLE'], 0)
461
462 def disable(self):
fabc7128
JF
463 """Disables the trace event in the kernel.
464
465 Disabling the group leader makes reading all counters under it
466 impossible.
467
468 """
f9bc9e65
JF
469 fcntl.ioctl(self.fd, ARCH.ioctl_numbers['DISABLE'], 0)
470
471 def reset(self):
fabc7128 472 """Resets the count of the trace event in the kernel."""
f9bc9e65
JF
473 fcntl.ioctl(self.fd, ARCH.ioctl_numbers['RESET'], 0)
474
692c7f6d 475
099a2dfc
SR
476class Provider(object):
477 """Encapsulates functionalities used by all providers."""
478 @staticmethod
479 def is_field_wanted(fields_filter, field):
480 """Indicate whether field is valid according to fields_filter."""
b74faa93 481 if not fields_filter:
099a2dfc
SR
482 return True
483 return re.match(fields_filter, field) is not None
484
485 @staticmethod
486 def walkdir(path):
487 """Returns os.walk() data for specified directory.
488
489 As it is only a wrapper it returns the same 3-tuple of (dirpath,
490 dirnames, filenames).
491 """
492 return next(os.walk(path))
493
494
495class TracepointProvider(Provider):
fabc7128
JF
496 """Data provider for the stats class.
497
498 Manages the events/groups from which it acquires its data.
499
500 """
c469117d 501 def __init__(self, pid, fields_filter):
f9bc9e65 502 self.group_leaders = []
099a2dfc 503 self.filters = self.get_filters()
c469117d
SR
504 self.update_fields(fields_filter)
505 self.pid = pid
f9bc9e65 506
099a2dfc
SR
507 @staticmethod
508 def get_filters():
509 """Returns a dict of trace events, their filter ids and
510 the values that can be filtered.
511
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.
516
517 """
518 filters = {}
519 filters['kvm_userspace_exit'] = ('reason', USERSPACE_EXIT_REASONS)
520 if ARCH.exit_reasons:
521 filters['kvm_exit'] = ('exit_reason', ARCH.exit_reasons)
522 return filters
523
f9bc9e65 524 def get_available_fields(self):
fabc7128
JF
525 """Returns a list of available event's of format 'event name(filter
526 name)'.
527
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.
532
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)
537
538 """
f9bc9e65 539 path = os.path.join(PATH_DEBUGFS_TRACING, 'events', 'kvm')
099a2dfc 540 fields = self.walkdir(path)[1]
f9bc9e65
JF
541 extra = []
542 for field in fields:
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 + ')')
547 fields += extra
548 return fields
549
c469117d
SR
550 def update_fields(self, fields_filter):
551 """Refresh fields, applying fields_filter"""
67c162b0
SR
552 self.fields = [field for field in self.get_available_fields()
553 if self.is_field_wanted(fields_filter, field)]
099a2dfc
SR
554
555 @staticmethod
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
560 integer ranges."""
561 integers = []
562 members = list_string.split(',')
563
564 for member in members:
565 if '-' not in member:
566 integers.append(int(member))
567 else:
568 int_range = member.split('-')
569 integers.extend(range(int(int_range[0]),
570 int(int_range[1]) + 1))
571
572 return integers
573
574 with open('/sys/devices/system/cpu/online') as cpu_list:
575 cpu_string = cpu_list.readline()
576 return parse_int_list(cpu_string)
c469117d 577
f9bc9e65 578 def setup_traces(self):
fabc7128
JF
579 """Creates all event and group objects needed to be able to retrieve
580 data."""
a1836069 581 fields = self.get_available_fields()
f0cf040f
JF
582 if self._pid > 0:
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')
099a2dfc 586 groupids = self.walkdir(path)[1]
f0cf040f 587 else:
099a2dfc 588 groupids = self.get_online_cpus()
f9bc9e65
JF
589
590 # The constant is needed as a buffer for python libs, std
591 # streams and other files that the script opens.
a1836069 592 newlim = len(groupids) * len(fields) + 50
f9bc9e65
JF
593 try:
594 softlim_, hardlim = resource.getrlimit(resource.RLIMIT_NOFILE)
595
596 if hardlim < newlim:
597 # Now we need CAP_SYS_RESOURCE, to increase the hard limit.
598 resource.setrlimit(resource.RLIMIT_NOFILE, (newlim, newlim))
599 else:
600 # Raising the soft limit is sufficient.
601 resource.setrlimit(resource.RLIMIT_NOFILE, (newlim, hardlim))
602
603 except ValueError:
604 sys.exit("NOFILE rlimit could not be raised to {0}".format(newlim))
605
f0cf040f 606 for groupid in groupids:
f9bc9e65 607 group = Group()
a1836069 608 for name in fields:
f9bc9e65
JF
609 tracepoint = name
610 tracefilter = None
611 match = re.match(r'(.*)\((.*)\)', name)
612 if match:
613 tracepoint, sub = match.groups()
614 tracefilter = ('%s==%d\0' %
615 (self.filters[tracepoint][0],
616 self.filters[tracepoint][1][sub]))
617
f0cf040f
JF
618 # From perf_event_open(2):
619 # pid > 0 and cpu == -1
620 # This measures the specified process/thread on any CPU.
621 #
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
626
f9bc9e65
JF
627 group.add_event(Event(name=name,
628 group=group,
f0cf040f
JF
629 trace_cpu=trace_cpu,
630 trace_pid=trace_pid,
f9bc9e65
JF
631 trace_point=tracepoint,
632 trace_filter=tracefilter))
f0cf040f 633
f9bc9e65
JF
634 self.group_leaders.append(group)
635
f9bc9e65
JF
636 @property
637 def fields(self):
638 return self._fields
639
640 @fields.setter
641 def fields(self, fields):
fabc7128 642 """Enables/disables the (un)wanted events"""
f9bc9e65
JF
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:
647 event.reset()
648 event.enable()
649 else:
650 # Do not disable the group leader.
651 # It would disable all of its events.
652 if index != 0:
653 event.disable()
654
f0cf040f
JF
655 @property
656 def pid(self):
657 return self._pid
658
659 @pid.setter
660 def pid(self, pid):
fabc7128 661 """Changes the monitored pid by setting new traces."""
f0cf040f 662 self._pid = pid
fabc7128
JF
663 # The garbage collector will get rid of all Event/Group
664 # objects and open files after removing the references.
f0cf040f
JF
665 self.group_leaders = []
666 self.setup_traces()
667 self.fields = self._fields
668
5c1954d2 669 def read(self, by_guest=0):
fabc7128 670 """Returns 'event name: current value' for all enabled events."""
f9bc9e65
JF
671 ret = defaultdict(int)
672 for group in self.group_leaders:
9cc5fbbb 673 for name, val in group.read().items():
f9bc9e65
JF
674 if name in self._fields:
675 ret[name] += val
676 return ret
677
9f114a03
SR
678 def reset(self):
679 """Reset all field counters"""
680 for group in self.group_leaders:
681 for event in group.events:
682 event.reset()
683
692c7f6d 684
099a2dfc 685class DebugfsProvider(Provider):
fabc7128
JF
686 """Provides data from the files that KVM creates in the kvm debugfs
687 folder."""
ab7ef193 688 def __init__(self, pid, fields_filter, include_past):
c469117d 689 self.update_fields(fields_filter)
9f114a03 690 self._baseline = {}
f0cf040f 691 self.do_read = True
e0ba3876 692 self.paths = []
c469117d 693 self.pid = pid
ab7ef193
SR
694 if include_past:
695 self.restore()
f9bc9e65
JF
696
697 def get_available_fields(self):
fabc7128
JF
698 """"Returns a list of available fields.
699
700 The fields are all available KVM debugfs files
701
702 """
099a2dfc 703 return self.walkdir(PATH_DEBUGFS_KVM)[2]
f9bc9e65 704
c469117d
SR
705 def update_fields(self, fields_filter):
706 """Refresh fields, applying fields_filter"""
707 self._fields = [field for field in self.get_available_fields()
099a2dfc 708 if self.is_field_wanted(fields_filter, field)]
c469117d 709
f9bc9e65
JF
710 @property
711 def fields(self):
712 return self._fields
713
714 @fields.setter
715 def fields(self, fields):
716 self._fields = fields
9f114a03 717 self.reset()
f9bc9e65 718
f0cf040f
JF
719 @property
720 def pid(self):
721 return self._pid
722
723 @pid.setter
724 def pid(self, pid):
c469117d 725 self._pid = pid
f0cf040f 726 if pid != 0:
099a2dfc 727 vms = self.walkdir(PATH_DEBUGFS_KVM)[1]
f0cf040f
JF
728 if len(vms) == 0:
729 self.do_read = False
730
731 self.paths = filter(lambda x: "{}-".format(pid) in x, vms)
732
733 else:
9f114a03 734 self.paths = []
f0cf040f 735 self.do_read = True
9f114a03 736 self.reset()
f0cf040f 737
5c1954d2 738 def read(self, reset=0, by_guest=0):
ab7ef193
SR
739 """Returns a dict with format:'file name / field -> current value'.
740
741 Parameter 'reset':
742 0 plain read
743 1 reset field counts to 0
744 2 restore the original field counts
745
746 """
f0cf040f
JF
747 results = {}
748
749 # If no debugfs filtering support is available, then don't read.
750 if not self.do_read:
751 return results
752
9f114a03
SR
753 paths = self.paths
754 if self._pid == 0:
755 paths = []
756 for entry in os.walk(PATH_DEBUGFS_KVM):
757 for dir in entry[1]:
758 paths.append(dir)
759 for path in paths:
f0cf040f 760 for field in self._fields:
9f114a03
SR
761 value = self.read_field(field, path)
762 key = path + field
ab7ef193 763 if reset == 1:
9f114a03 764 self._baseline[key] = value
ab7ef193
SR
765 if reset == 2:
766 self._baseline[key] = 0
9f114a03
SR
767 if self._baseline.get(key, -1) == -1:
768 self._baseline[key] = value
5c1954d2
SR
769 increment = (results.get(field, 0) + value -
770 self._baseline.get(key, 0))
771 if by_guest:
772 pid = key.split('-')[0]
773 if pid in results:
774 results[pid] += increment
775 else:
776 results[pid] = increment
777 else:
778 results[field] = increment
f0cf040f
JF
779
780 return results
781
782 def read_field(self, field, path):
783 """Returns the value of a single field from a specific VM."""
784 try:
785 return int(open(os.path.join(PATH_DEBUGFS_KVM,
786 path,
787 field))
788 .read())
789 except IOError:
790 return 0
f9bc9e65 791
9f114a03
SR
792 def reset(self):
793 """Reset field counters"""
794 self._baseline = {}
795 self.read(1)
796
ab7ef193
SR
797 def restore(self):
798 """Reset field counters"""
799 self._baseline = {}
800 self.read(2)
801
692c7f6d 802
f9bc9e65 803class Stats(object):
fabc7128
JF
804 """Manages the data providers and the data they provide.
805
806 It is used to set filters on the provider's data and collect all
807 provider data.
808
809 """
c469117d 810 def __init__(self, options):
099a2dfc 811 self.providers = self.get_providers(options)
c469117d
SR
812 self._pid_filter = options.pid
813 self._fields_filter = options.fields
f9bc9e65 814 self.values = {}
f9bc9e65 815
099a2dfc
SR
816 @staticmethod
817 def get_providers(options):
818 """Returns a list of data providers depending on the passed options."""
819 providers = []
820
821 if options.debugfs:
ab7ef193
SR
822 providers.append(DebugfsProvider(options.pid, options.fields,
823 options.dbgfs_include_past))
099a2dfc
SR
824 if options.tracepoints or not providers:
825 providers.append(TracepointProvider(options.pid, options.fields))
826
827 return providers
828
f9bc9e65 829 def update_provider_filters(self):
fabc7128 830 """Propagates fields filters to providers."""
f9bc9e65
JF
831 # As we reset the counters when updating the fields we can
832 # also clear the cache of old values.
833 self.values = {}
834 for provider in self.providers:
c469117d 835 provider.update_fields(self._fields_filter)
f0cf040f 836
9f114a03
SR
837 def reset(self):
838 self.values = {}
839 for provider in self.providers:
840 provider.reset()
841
f9bc9e65
JF
842 @property
843 def fields_filter(self):
844 return self._fields_filter
845
846 @fields_filter.setter
847 def fields_filter(self, fields_filter):
9f114a03
SR
848 if fields_filter != self._fields_filter:
849 self._fields_filter = fields_filter
850 self.update_provider_filters()
f9bc9e65 851
f0cf040f
JF
852 @property
853 def pid_filter(self):
854 return self._pid_filter
855
856 @pid_filter.setter
857 def pid_filter(self, pid):
9f114a03
SR
858 if pid != self._pid_filter:
859 self._pid_filter = pid
860 self.values = {}
c469117d
SR
861 for provider in self.providers:
862 provider.pid = self._pid_filter
f0cf040f 863
5c1954d2 864 def get(self, by_guest=0):
fabc7128
JF
865 """Returns a dict with field -> (value, delta to last value) of all
866 provider data."""
f9bc9e65 867 for provider in self.providers:
5c1954d2
SR
868 new = provider.read(by_guest=by_guest)
869 for key in new if by_guest else provider.fields:
9f114a03 870 oldval = self.values.get(key, (0, 0))[0]
f9bc9e65 871 newval = new.get(key, 0)
9f114a03 872 newdelta = newval - oldval
f9bc9e65
JF
873 self.values[key] = (newval, newdelta)
874 return self.values
875
5c1954d2
SR
876 def toggle_display_guests(self, to_pid):
877 """Toggle between collection of stats by individual event and by
878 guest pid
879
880 Events reported by DebugfsProvider change when switching to/from
881 reading by guest values. Hence we have to remove the excess event
882 names from self.values.
883
884 """
885 if any(isinstance(ins, TracepointProvider) for ins in self.providers):
886 return 1
887 if to_pid:
888 for provider in self.providers:
889 if isinstance(provider, DebugfsProvider):
890 for key in provider.fields:
891 if key in self.values.keys():
892 del self.values[key]
893 else:
894 oldvals = self.values.copy()
895 for key in oldvals:
896 if key.isdigit():
897 del self.values[key]
898 # Update oldval (see get())
899 self.get(to_pid)
900 return 0
901
64eefad2 902DELAY_DEFAULT = 3.0
a24e85f6 903MAX_GUEST_NAME_LEN = 48
72187dfa 904MAX_REGEX_LEN = 44
4443084f 905DEFAULT_REGEX = r'^[^\(]*$'
6667ae8f 906SORT_DEFAULT = 0
f9bc9e65 907
692c7f6d 908
f9bc9e65 909class Tui(object):
fabc7128 910 """Instruments curses to draw a nice text ui."""
f9bc9e65
JF
911 def __init__(self, stats):
912 self.stats = stats
913 self.screen = None
64eefad2
SR
914 self._delay_initial = 0.25
915 self._delay_regular = DELAY_DEFAULT
6667ae8f 916 self._sorting = SORT_DEFAULT
5c1954d2 917 self._display_guests = 0
f9bc9e65
JF
918
919 def __enter__(self):
920 """Initialises curses for later use. Based on curses.wrapper
921 implementation from the Python standard library."""
922 self.screen = curses.initscr()
923 curses.noecho()
924 curses.cbreak()
925
926 # The try/catch works around a minor bit of
927 # over-conscientiousness in the curses module, the error
928 # return from C start_color() is ignorable.
929 try:
930 curses.start_color()
9fc0adfc 931 except curses.error:
f9bc9e65
JF
932 pass
933
a0b4e6a0
SR
934 # Hide cursor in extra statement as some monochrome terminals
935 # might support hiding but not colors.
936 try:
937 curses.curs_set(0)
938 except curses.error:
939 pass
940
f9bc9e65
JF
941 curses.use_default_colors()
942 return self
943
944 def __exit__(self, *exception):
773bffee 945 """Resets the terminal to its normal state. Based on curses.wrapper
f9bc9e65
JF
946 implementation from the Python standard library."""
947 if self.screen:
948 self.screen.keypad(0)
949 curses.echo()
950 curses.nocbreak()
951 curses.endwin()
952
19e8e54f
SR
953 @staticmethod
954 def get_all_gnames():
865279c5
SR
955 """Returns a list of (pid, gname) tuples of all running guests"""
956 res = []
099a2dfc
SR
957 try:
958 child = subprocess.Popen(['ps', '-A', '--format', 'pid,args'],
959 stdout=subprocess.PIPE)
960 except:
961 raise Exception
962 for line in child.stdout:
9cc5fbbb 963 line = line.decode(ENCODING).lstrip().split(' ', 1)
099a2dfc
SR
964 # perform a sanity check before calling the more expensive
965 # function to possibly extract the guest name
865279c5 966 if ' -name ' in line[1]:
19e8e54f 967 res.append((line[0], Tui.get_gname_from_pid(line[0])))
099a2dfc
SR
968 child.stdout.close()
969
865279c5
SR
970 return res
971
972 def print_all_gnames(self, row):
973 """Print a list of all running guests along with their pids."""
974 self.screen.addstr(row, 2, '%8s %-60s' %
975 ('Pid', 'Guest Name (fuzzy list, might be '
976 'inaccurate!)'),
977 curses.A_UNDERLINE)
978 row += 1
979 try:
980 for line in self.get_all_gnames():
981 self.screen.addstr(row, 2, '%8s %-60s' % (line[0], line[1]))
982 row += 1
983 if row >= self.screen.getmaxyx()[0]:
984 break
985 except Exception:
986 self.screen.addstr(row + 1, 2, 'Not available')
987
19e8e54f
SR
988 @staticmethod
989 def get_pid_from_gname(gname):
865279c5
SR
990 """Fuzzy function to convert guest name to QEMU process pid.
991
992 Returns a list of potential pids, can be empty if no match found.
993 Throws an exception on processing errors.
994
995 """
996 pids = []
19e8e54f 997 for line in Tui.get_all_gnames():
865279c5
SR
998 if gname == line[1]:
999 pids.append(int(line[0]))
1000
099a2dfc
SR
1001 return pids
1002
1003 @staticmethod
1004 def get_gname_from_pid(pid):
1005 """Returns the guest name for a QEMU process pid.
1006
1007 Extracts the guest name from the QEMU comma line by processing the
1008 '-name' option. Will also handle names specified out of sequence.
1009
1010 """
1011 name = ''
1012 try:
1013 line = open('/proc/{}/cmdline'
9cc5fbbb 1014 .format(pid), 'r').read().split('\0')
099a2dfc
SR
1015 parms = line[line.index('-name') + 1].split(',')
1016 while '' in parms:
1017 # commas are escaped (i.e. ',,'), hence e.g. 'foo,bar' results
1018 # in # ['foo', '', 'bar'], which we revert here
1019 idx = parms.index('')
1020 parms[idx - 1] += ',' + parms[idx + 1]
1021 del parms[idx:idx+2]
1022 # the '-name' switch allows for two ways to specify the guest name,
1023 # where the plain name overrides the name specified via 'guest='
1024 for arg in parms:
1025 if '=' not in arg:
1026 name = arg
1027 break
1028 if arg[:6] == 'guest=':
1029 name = arg[6:]
1030 except (ValueError, IOError, IndexError):
1031 pass
1032
1033 return name
1034
f9bc9e65 1035 def update_drilldown(self):
fabc7128 1036 """Sets or removes a filter that only allows fields without braces."""
f9bc9e65 1037 if not self.stats.fields_filter:
4443084f 1038 self.stats.fields_filter = DEFAULT_REGEX
f9bc9e65 1039
4443084f 1040 elif self.stats.fields_filter == DEFAULT_REGEX:
f9bc9e65
JF
1041 self.stats.fields_filter = None
1042
f0cf040f 1043 def update_pid(self, pid):
fabc7128 1044 """Propagates pid selection to stats object."""
f0cf040f
JF
1045 self.stats.pid_filter = pid
1046
184b2d23
SR
1047 def refresh_header(self, pid=None):
1048 """Refreshes the header."""
1049 if pid is None:
1050 pid = self.stats.pid_filter
f9bc9e65 1051 self.screen.erase()
099a2dfc 1052 gname = self.get_gname_from_pid(pid)
a24e85f6
SR
1053 if gname:
1054 gname = ('({})'.format(gname[:MAX_GUEST_NAME_LEN] + '...'
1055 if len(gname) > MAX_GUEST_NAME_LEN
1056 else gname))
184b2d23 1057 if pid > 0:
a24e85f6
SR
1058 self.screen.addstr(0, 0, 'kvm statistics - pid {0} {1}'
1059 .format(pid, gname), curses.A_BOLD)
f0cf040f
JF
1060 else:
1061 self.screen.addstr(0, 0, 'kvm statistics - summary', curses.A_BOLD)
4443084f
SR
1062 if self.stats.fields_filter and self.stats.fields_filter \
1063 != DEFAULT_REGEX:
72187dfa
SR
1064 regex = self.stats.fields_filter
1065 if len(regex) > MAX_REGEX_LEN:
1066 regex = regex[:MAX_REGEX_LEN] + '...'
1067 self.screen.addstr(1, 17, 'regex filter: {0}'.format(regex))
5c1954d2
SR
1068 if self._display_guests:
1069 col_name = 'Guest Name'
1070 else:
1071 col_name = 'Event'
38e89c37 1072 self.screen.addstr(2, 1, '%-40s %10s%7s %8s' %
5c1954d2 1073 (col_name, 'Total', '%Total', 'CurAvg/s'),
f6d75310 1074 curses.A_STANDOUT)
184b2d23
SR
1075 self.screen.addstr(4, 1, 'Collecting data...')
1076 self.screen.refresh()
1077
1078 def refresh_body(self, sleeptime):
f9bc9e65 1079 row = 3
184b2d23
SR
1080 self.screen.move(row, 0)
1081 self.screen.clrtobot()
5c1954d2 1082 stats = self.stats.get(self._display_guests)
692c7f6d 1083
6667ae8f
SR
1084 def sortCurAvg(x):
1085 # sort by current events if available
f9bc9e65
JF
1086 if stats[x][1]:
1087 return (-stats[x][1], -stats[x][0])
1088 else:
1089 return (0, -stats[x][0])
6667ae8f
SR
1090
1091 def sortTotal(x):
1092 # sort by totals
1093 return (0, -stats[x][0])
e55fe3cc 1094 total = 0.
fff8c9eb
SR
1095 for key in stats.keys():
1096 if key.find('(') is -1:
1097 total += stats[key][0]
6667ae8f
SR
1098 if self._sorting == SORT_DEFAULT:
1099 sortkey = sortCurAvg
1100 else:
1101 sortkey = sortTotal
cf656c76 1102 tavg = 0
f9bc9e65 1103 for key in sorted(stats.keys(), key=sortkey):
cf656c76 1104 if row >= self.screen.getmaxyx()[0] - 1:
f9bc9e65
JF
1105 break
1106 values = stats[key]
1107 if not values[0] and not values[1]:
1108 break
5a7d11f8
SR
1109 if values[0] is not None:
1110 cur = int(round(values[1] / sleeptime)) if values[1] else ''
5c1954d2
SR
1111 if self._display_guests:
1112 key = self.get_gname_from_pid(key)
38e89c37 1113 self.screen.addstr(row, 1, '%-40s %10d%7.1f %8s' %
5a7d11f8
SR
1114 (key, values[0], values[0] * 100 / total,
1115 cur))
cf656c76
SR
1116 if cur is not '' and key.find('(') is -1:
1117 tavg += cur
f9bc9e65 1118 row += 1
57253937
SR
1119 if row == 3:
1120 self.screen.addstr(4, 1, 'No matching events reported yet')
cf656c76
SR
1121 else:
1122 self.screen.addstr(row, 1, '%-40s %10d %8s' %
1123 ('Total', total, tavg if tavg else ''),
1124 curses.A_BOLD)
f9bc9e65
JF
1125 self.screen.refresh()
1126
5c1954d2
SR
1127 def show_msg(self, text):
1128 """Display message centered text and exit on key press"""
1129 hint = 'Press any key to continue'
1130 curses.cbreak()
1131 self.screen.erase()
1132 (x, term_width) = self.screen.getmaxyx()
1133 row = 2
1134 for line in text:
1135 start = (term_width - len(line)) / 2
1136 self.screen.addstr(row, start, line)
1137 row += 1
1138 self.screen.addstr(row + 1, (term_width - len(hint)) / 2, hint,
1139 curses.A_STANDOUT)
1140 self.screen.getkey()
1141
1fdea7b2
SR
1142 def show_help_interactive(self):
1143 """Display help with list of interactive commands"""
5c1954d2
SR
1144 msg = (' b toggle events by guests (debugfs only, honors'
1145 ' filters)',
1146 ' c clear filter',
1fdea7b2
SR
1147 ' f filter by regular expression',
1148 ' g filter by guest name',
1149 ' h display interactive commands reference',
6667ae8f 1150 ' o toggle sorting order (Total vs CurAvg/s)',
1fdea7b2
SR
1151 ' p filter by PID',
1152 ' q quit',
1153 ' r reset stats',
64eefad2 1154 ' s set update interval',
1fdea7b2
SR
1155 ' x toggle reporting of stats for individual child trace'
1156 ' events',
1157 'Any other key refreshes statistics immediately')
1158 curses.cbreak()
1159 self.screen.erase()
1160 self.screen.addstr(0, 0, "Interactive commands reference",
1161 curses.A_BOLD)
1162 self.screen.addstr(2, 0, "Press any key to exit", curses.A_STANDOUT)
1163 row = 4
1164 for line in msg:
1165 self.screen.addstr(row, 0, line)
1166 row += 1
1167 self.screen.getkey()
1168 self.refresh_header()
1169
f9bc9e65 1170 def show_filter_selection(self):
fabc7128
JF
1171 """Draws filter selection mask.
1172
1173 Asks for a valid regex and sets the fields filter accordingly.
1174
1175 """
f9bc9e65
JF
1176 while True:
1177 self.screen.erase()
1178 self.screen.addstr(0, 0,
1179 "Show statistics for events matching a regex.",
1180 curses.A_BOLD)
1181 self.screen.addstr(2, 0,
1182 "Current regex: {0}"
1183 .format(self.stats.fields_filter))
1184 self.screen.addstr(3, 0, "New regex: ")
1185 curses.echo()
9cc5fbbb 1186 regex = self.screen.getstr().decode(ENCODING)
f9bc9e65
JF
1187 curses.noecho()
1188 if len(regex) == 0:
4443084f 1189 self.stats.fields_filter = DEFAULT_REGEX
184b2d23 1190 self.refresh_header()
f9bc9e65
JF
1191 return
1192 try:
1193 re.compile(regex)
1194 self.stats.fields_filter = regex
184b2d23 1195 self.refresh_header()
f9bc9e65
JF
1196 return
1197 except re.error:
1198 continue
1199
f9ff1087 1200 def show_vm_selection_by_pid(self):
fabc7128
JF
1201 """Draws PID selection mask.
1202
1203 Asks for a pid until a valid pid or 0 has been entered.
1204
1205 """
0152c20f 1206 msg = ''
f0cf040f
JF
1207 while True:
1208 self.screen.erase()
1209 self.screen.addstr(0, 0,
1210 'Show statistics for specific pid.',
1211 curses.A_BOLD)
1212 self.screen.addstr(1, 0,
1213 'This might limit the shown data to the trace '
1214 'statistics.')
0152c20f 1215 self.screen.addstr(5, 0, msg)
865279c5 1216 self.print_all_gnames(7)
f0cf040f
JF
1217
1218 curses.echo()
1219 self.screen.addstr(3, 0, "Pid [0 or pid]: ")
9cc5fbbb 1220 pid = self.screen.getstr().decode(ENCODING)
f0cf040f
JF
1221 curses.noecho()
1222
1223 try:
be03ea3b
SR
1224 if len(pid) > 0:
1225 pid = int(pid)
1226 if pid != 0 and not os.path.isdir(os.path.join('/proc/',
1227 str(pid))):
0152c20f 1228 msg = '"' + str(pid) + '": Not a running process'
be03ea3b
SR
1229 continue
1230 else:
1231 pid = 0
184b2d23
SR
1232 self.refresh_header(pid)
1233 self.update_pid(pid)
1234 break
f0cf040f 1235 except ValueError:
0152c20f 1236 msg = '"' + str(pid) + '": Not a valid pid'
f0cf040f 1237
64eefad2
SR
1238 def show_set_update_interval(self):
1239 """Draws update interval selection mask."""
1240 msg = ''
1241 while True:
1242 self.screen.erase()
1243 self.screen.addstr(0, 0, 'Set update interval (defaults to %fs).' %
1244 DELAY_DEFAULT, curses.A_BOLD)
1245 self.screen.addstr(4, 0, msg)
1246 self.screen.addstr(2, 0, 'Change delay from %.1fs to ' %
1247 self._delay_regular)
1248 curses.echo()
9cc5fbbb 1249 val = self.screen.getstr().decode(ENCODING)
64eefad2
SR
1250 curses.noecho()
1251
1252 try:
1253 if len(val) > 0:
1254 delay = float(val)
1255 if delay < 0.1:
1256 msg = '"' + str(val) + '": Value must be >=0.1'
1257 continue
1258 if delay > 25.5:
1259 msg = '"' + str(val) + '": Value must be <=25.5'
1260 continue
1261 else:
1262 delay = DELAY_DEFAULT
1263 self._delay_regular = delay
1264 break
1265
1266 except ValueError:
1267 msg = '"' + str(val) + '": Invalid value'
1268 self.refresh_header()
1269
f9ff1087
SR
1270 def show_vm_selection_by_guest_name(self):
1271 """Draws guest selection mask.
1272
1273 Asks for a guest name until a valid guest name or '' is entered.
1274
1275 """
1276 msg = ''
1277 while True:
1278 self.screen.erase()
1279 self.screen.addstr(0, 0,
1280 'Show statistics for specific guest.',
1281 curses.A_BOLD)
1282 self.screen.addstr(1, 0,
1283 'This might limit the shown data to the trace '
1284 'statistics.')
1285 self.screen.addstr(5, 0, msg)
61f381bb 1286 self.print_all_gnames(7)
f9ff1087
SR
1287 curses.echo()
1288 self.screen.addstr(3, 0, "Guest [ENTER or guest]: ")
9cc5fbbb 1289 gname = self.screen.getstr().decode(ENCODING)
f9ff1087
SR
1290 curses.noecho()
1291
1292 if not gname:
1293 self.refresh_header(0)
1294 self.update_pid(0)
1295 break
1296 else:
1297 pids = []
1298 try:
099a2dfc 1299 pids = self.get_pid_from_gname(gname)
f9ff1087
SR
1300 except:
1301 msg = '"' + gname + '": Internal error while searching, ' \
1302 'use pid filter instead'
1303 continue
1304 if len(pids) == 0:
1305 msg = '"' + gname + '": Not an active guest'
1306 continue
1307 if len(pids) > 1:
1308 msg = '"' + gname + '": Multiple matches found, use pid ' \
1309 'filter instead'
1310 continue
1311 self.refresh_header(pids[0])
1312 self.update_pid(pids[0])
1313 break
1314
f9bc9e65 1315 def show_stats(self):
fabc7128 1316 """Refreshes the screen and processes user input."""
64eefad2 1317 sleeptime = self._delay_initial
184b2d23 1318 self.refresh_header()
124c2fc9 1319 start = 0.0 # result based on init value never appears on screen
f9bc9e65 1320 while True:
124c2fc9 1321 self.refresh_body(time.time() - start)
f9bc9e65 1322 curses.halfdelay(int(sleeptime * 10))
124c2fc9 1323 start = time.time()
64eefad2 1324 sleeptime = self._delay_regular
f9bc9e65
JF
1325 try:
1326 char = self.screen.getkey()
5c1954d2
SR
1327 if char == 'b':
1328 self._display_guests = not self._display_guests
1329 if self.stats.toggle_display_guests(self._display_guests):
1330 self.show_msg(['Command not available with tracepoints'
1331 ' enabled', 'Restart with debugfs only '
1332 '(see option \'-d\') and try again!'])
1333 self._display_guests = not self._display_guests
1334 self.refresh_header()
4443084f
SR
1335 if char == 'c':
1336 self.stats.fields_filter = DEFAULT_REGEX
1337 self.refresh_header(0)
1338 self.update_pid(0)
f9bc9e65 1339 if char == 'f':
62d1b6cc 1340 curses.curs_set(1)
f9bc9e65 1341 self.show_filter_selection()
62d1b6cc 1342 curses.curs_set(0)
64eefad2 1343 sleeptime = self._delay_initial
f9ff1087 1344 if char == 'g':
62d1b6cc 1345 curses.curs_set(1)
f9ff1087 1346 self.show_vm_selection_by_guest_name()
62d1b6cc 1347 curses.curs_set(0)
64eefad2 1348 sleeptime = self._delay_initial
1fdea7b2
SR
1349 if char == 'h':
1350 self.show_help_interactive()
6667ae8f
SR
1351 if char == 'o':
1352 self._sorting = not self._sorting
f0cf040f 1353 if char == 'p':
62d1b6cc 1354 curses.curs_set(1)
f9ff1087 1355 self.show_vm_selection_by_pid()
62d1b6cc 1356 curses.curs_set(0)
64eefad2 1357 sleeptime = self._delay_initial
1fdea7b2
SR
1358 if char == 'q':
1359 break
9f114a03 1360 if char == 'r':
9f114a03 1361 self.stats.reset()
64eefad2
SR
1362 if char == 's':
1363 curses.curs_set(1)
1364 self.show_set_update_interval()
1365 curses.curs_set(0)
1366 sleeptime = self._delay_initial
1fdea7b2
SR
1367 if char == 'x':
1368 self.update_drilldown()
ab7ef193 1369 # prevents display of current values on next refresh
faa06650 1370 self.stats.get(self._display_guests)
f9bc9e65
JF
1371 except KeyboardInterrupt:
1372 break
1373 except curses.error:
1374 continue
1375
692c7f6d 1376
f9bc9e65 1377def batch(stats):
fabc7128 1378 """Prints statistics in a key, value format."""
dadf1e78
SR
1379 try:
1380 s = stats.get()
1381 time.sleep(1)
1382 s = stats.get()
1383 for key in sorted(s.keys()):
1384 values = s[key]
9cc5fbbb 1385 print('%-42s%10d%10d' % (key, values[0], values[1]))
dadf1e78
SR
1386 except KeyboardInterrupt:
1387 pass
f9bc9e65 1388
692c7f6d 1389
f9bc9e65 1390def log(stats):
fabc7128 1391 """Prints statistics as reiterating key block, multiple value blocks."""
9cc5fbbb 1392 keys = sorted(stats.get().keys())
692c7f6d 1393
f9bc9e65
JF
1394 def banner():
1395 for k in keys:
9cc5fbbb
JC
1396 print(k, end=' ')
1397 print()
692c7f6d 1398
f9bc9e65
JF
1399 def statline():
1400 s = stats.get()
1401 for k in keys:
9cc5fbbb
JC
1402 print(' %9d' % s[k][1], end=' ')
1403 print()
f9bc9e65
JF
1404 line = 0
1405 banner_repeat = 20
1406 while True:
dadf1e78
SR
1407 try:
1408 time.sleep(1)
1409 if line % banner_repeat == 0:
1410 banner()
1411 statline()
1412 line += 1
1413 except KeyboardInterrupt:
1414 break
f9bc9e65 1415
692c7f6d 1416
f9bc9e65 1417def get_options():
fabc7128 1418 """Returns processed program arguments."""
f9bc9e65
JF
1419 description_text = """
1420This script displays various statistics about VMs running under KVM.
1421The statistics are gathered from the KVM debugfs entries and / or the
1422currently available perf traces.
1423
1424The monitoring takes additional cpu cycles and might affect the VM's
1425performance.
1426
1427Requirements:
1428- Access to:
efcb5219
LM
1429 %s
1430 %s/events/*
f9bc9e65
JF
1431 /proc/pid/task
1432- /proc/sys/kernel/perf_event_paranoid < 1 if user has no
1433 CAP_SYS_ADMIN and perf events are used.
1434- CAP_SYS_RESOURCE if the hard limit is not high enough to allow
1435 the large number of files that are possibly opened.
1eaa2f90
SR
1436
1437Interactive Commands:
5c1954d2 1438 b toggle events by guests (debugfs only, honors filters)
4443084f 1439 c clear filter
1eaa2f90 1440 f filter by regular expression
f9ff1087 1441 g filter by guest name
1fdea7b2 1442 h display interactive commands reference
6667ae8f 1443 o toggle sorting order (Total vs CurAvg/s)
1eaa2f90
SR
1444 p filter by PID
1445 q quit
9f114a03 1446 r reset stats
64eefad2 1447 s set update interval
1fdea7b2 1448 x toggle reporting of stats for individual child trace events
1eaa2f90 1449Press any other key to refresh statistics immediately.
efcb5219 1450""" % (PATH_DEBUGFS_KVM, PATH_DEBUGFS_TRACING)
f9bc9e65
JF
1451
1452 class PlainHelpFormatter(optparse.IndentedHelpFormatter):
1453 def format_description(self, description):
1454 if description:
1455 return description + "\n"
1456 else:
1457 return ""
1458
f9ff1087
SR
1459 def cb_guest_to_pid(option, opt, val, parser):
1460 try:
099a2dfc 1461 pids = Tui.get_pid_from_gname(val)
f9ff1087 1462 except:
822cfe3e
SR
1463 sys.exit('Error while searching for guest "{}". Use "-p" to '
1464 'specify a pid instead?'.format(val))
f9ff1087 1465 if len(pids) == 0:
822cfe3e 1466 sys.exit('Error: No guest by the name "{}" found'.format(val))
f9ff1087 1467 if len(pids) > 1:
822cfe3e
SR
1468 sys.exit('Error: Multiple processes found (pids: {}). Use "-p" '
1469 'to specify the desired pid'.format(" ".join(pids)))
f9ff1087
SR
1470 parser.values.pid = pids[0]
1471
f9bc9e65
JF
1472 optparser = optparse.OptionParser(description=description_text,
1473 formatter=PlainHelpFormatter())
1474 optparser.add_option('-1', '--once', '--batch',
1475 action='store_true',
1476 default=False,
1477 dest='once',
1478 help='run in batch mode for one second',
1479 )
ab7ef193
SR
1480 optparser.add_option('-i', '--debugfs-include-past',
1481 action='store_true',
1482 default=False,
1483 dest='dbgfs_include_past',
1484 help='include all available data on past events for '
1485 'debugfs',
1486 )
f9bc9e65
JF
1487 optparser.add_option('-l', '--log',
1488 action='store_true',
1489 default=False,
1490 dest='log',
1491 help='run in logging mode (like vmstat)',
1492 )
1493 optparser.add_option('-t', '--tracepoints',
1494 action='store_true',
1495 default=False,
1496 dest='tracepoints',
1497 help='retrieve statistics from tracepoints',
1498 )
1499 optparser.add_option('-d', '--debugfs',
1500 action='store_true',
1501 default=False,
1502 dest='debugfs',
1503 help='retrieve statistics from debugfs',
1504 )
1505 optparser.add_option('-f', '--fields',
1506 action='store',
c469117d 1507 default=DEFAULT_REGEX,
f9bc9e65 1508 dest='fields',
67fbcd62
LM
1509 help='''fields to display (regex)
1510 "-f help" for a list of available events''',
f9bc9e65 1511 )
f0cf040f 1512 optparser.add_option('-p', '--pid',
e0ba3876
SR
1513 action='store',
1514 default=0,
1515 type='int',
1516 dest='pid',
1517 help='restrict statistics to pid',
1518 )
f9ff1087
SR
1519 optparser.add_option('-g', '--guest',
1520 action='callback',
1521 type='string',
1522 dest='pid',
1523 metavar='GUEST',
1524 help='restrict statistics to guest by name',
1525 callback=cb_guest_to_pid,
1526 )
73fab6ff
SR
1527 options, unkn = optparser.parse_args(sys.argv)
1528 if len(unkn) != 1:
1529 sys.exit('Error: Extra argument(s): ' + ' '.join(unkn[1:]))
08e20a63
SR
1530 try:
1531 # verify that we were passed a valid regex up front
1532 re.compile(options.fields)
1533 except re.error:
1534 sys.exit('Error: "' + options.fields + '" is not a valid regular '
1535 'expression')
1536
f9bc9e65
JF
1537 return options
1538
692c7f6d 1539
f9bc9e65 1540def check_access(options):
fabc7128 1541 """Exits if the current user can't access all needed directories."""
f9bc9e65
JF
1542 if not os.path.exists('/sys/kernel/debug'):
1543 sys.stderr.write('Please enable CONFIG_DEBUG_FS in your kernel.')
1544 sys.exit(1)
1545
1546 if not os.path.exists(PATH_DEBUGFS_KVM):
1547 sys.stderr.write("Please make sure, that debugfs is mounted and "
1548 "readable by the current user:\n"
1549 "('mount -t debugfs debugfs /sys/kernel/debug')\n"
1550 "Also ensure, that the kvm modules are loaded.\n")
1551 sys.exit(1)
1552
e0ba3876
SR
1553 if not os.path.exists(PATH_DEBUGFS_TRACING) and (options.tracepoints or
1554 not options.debugfs):
f9bc9e65
JF
1555 sys.stderr.write("Please enable CONFIG_TRACING in your kernel "
1556 "when using the option -t (default).\n"
1557 "If it is enabled, make {0} readable by the "
1558 "current user.\n"
1559 .format(PATH_DEBUGFS_TRACING))
1560 if options.tracepoints:
1561 sys.exit(1)
1562
1563 sys.stderr.write("Falling back to debugfs statistics!\n")
1564 options.debugfs = True
e0ba3876 1565 time.sleep(5)
f9bc9e65
JF
1566
1567 return options
1568
692c7f6d 1569
f9bc9e65
JF
1570def main():
1571 options = get_options()
1572 options = check_access(options)
f0cf040f
JF
1573
1574 if (options.pid > 0 and
1575 not os.path.isdir(os.path.join('/proc/',
1576 str(options.pid)))):
1577 sys.stderr.write('Did you use a (unsupported) tid instead of a pid?\n')
1578 sys.exit('Specified pid does not exist.')
1579
c469117d 1580 stats = Stats(options)
f9bc9e65 1581
aa12f594 1582 if options.fields == 'help':
b74faa93 1583 stats.fields_filter = None
aa12f594
SR
1584 event_list = []
1585 for key in stats.get().keys():
1586 event_list.append(key.split('(', 1)[0])
1587 sys.stdout.write(' ' + '\n '.join(sorted(set(event_list))) + '\n')
1588 sys.exit(0)
67fbcd62 1589
f9bc9e65
JF
1590 if options.log:
1591 log(stats)
1592 elif not options.once:
1593 with Tui(stats) as tui:
1594 tui.show_stats()
1595 else:
1596 batch(stats)
1597
1598if __name__ == "__main__":
1599 main()