tools/kvm_stat: fix python3 issues
[linux-2.6-block.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
006f1548 36from collections import defaultdict, namedtuple
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 230ENCODING = locale.getpreferredencoding(False)
18e8f410 231TRACE_FILTER = re.compile(r'^[^\(]*$')
9cc5fbbb 232
692c7f6d 233
f9bc9e65 234class Arch(object):
fabc7128
JF
235 """Encapsulates global architecture specific data.
236
237 Contains the performance event open syscall and ioctl numbers, as
238 well as the VM exit reasons for the architecture it runs on.
f9bc9e65
JF
239
240 """
241 @staticmethod
242 def get_arch():
243 machine = os.uname()[4]
244
245 if machine.startswith('ppc'):
246 return ArchPPC()
247 elif machine.startswith('aarch64'):
248 return ArchA64()
249 elif machine.startswith('s390'):
250 return ArchS390()
251 else:
252 # X86_64
253 for line in open('/proc/cpuinfo'):
254 if not line.startswith('flags'):
255 continue
256
257 flags = line.split()
258 if 'vmx' in flags:
259 return ArchX86(VMX_EXIT_REASONS)
260 if 'svm' in flags:
261 return ArchX86(SVM_EXIT_REASONS)
262 return
263
18e8f410
SR
264 def tracepoint_is_child(self, field):
265 if (TRACE_FILTER.match(field)):
266 return None
267 return field.split('(', 1)[0]
268
692c7f6d 269
f9bc9e65
JF
270class ArchX86(Arch):
271 def __init__(self, exit_reasons):
272 self.sc_perf_evt_open = 298
273 self.ioctl_numbers = IOCTL_NUMBERS
274 self.exit_reasons = exit_reasons
275
18e8f410
SR
276 def debugfs_is_child(self, field):
277 """ Returns name of parent if 'field' is a child, None otherwise """
278 return None
279
692c7f6d 280
f9bc9e65
JF
281class ArchPPC(Arch):
282 def __init__(self):
283 self.sc_perf_evt_open = 319
284 self.ioctl_numbers = IOCTL_NUMBERS
285 self.ioctl_numbers['ENABLE'] = 0x20002400
286 self.ioctl_numbers['DISABLE'] = 0x20002401
c7d4fb5a 287 self.ioctl_numbers['RESET'] = 0x20002403
f9bc9e65
JF
288
289 # PPC comes in 32 and 64 bit and some generated ioctl
290 # numbers depend on the wordsize.
291 char_ptr_size = ctypes.sizeof(ctypes.c_char_p)
292 self.ioctl_numbers['SET_FILTER'] = 0x80002406 | char_ptr_size << 16
c7d4fb5a 293 self.exit_reasons = {}
f9bc9e65 294
18e8f410
SR
295 def debugfs_is_child(self, field):
296 """ Returns name of parent if 'field' is a child, None otherwise """
297 return None
298
692c7f6d 299
f9bc9e65
JF
300class ArchA64(Arch):
301 def __init__(self):
302 self.sc_perf_evt_open = 241
303 self.ioctl_numbers = IOCTL_NUMBERS
304 self.exit_reasons = AARCH64_EXIT_REASONS
305
18e8f410
SR
306 def debugfs_is_child(self, field):
307 """ Returns name of parent if 'field' is a child, None otherwise """
308 return None
309
692c7f6d 310
f9bc9e65
JF
311class ArchS390(Arch):
312 def __init__(self):
313 self.sc_perf_evt_open = 331
314 self.ioctl_numbers = IOCTL_NUMBERS
315 self.exit_reasons = None
316
18e8f410
SR
317 def debugfs_is_child(self, field):
318 """ Returns name of parent if 'field' is a child, None otherwise """
319 if field.startswith('instruction_'):
320 return 'exit_instruction'
321
322
f9bc9e65
JF
323ARCH = Arch.get_arch()
324
325
f9bc9e65 326class perf_event_attr(ctypes.Structure):
fabc7128
JF
327 """Struct that holds the necessary data to set up a trace event.
328
329 For an extensive explanation see perf_event_open(2) and
330 include/uapi/linux/perf_event.h, struct perf_event_attr
331
332 All fields that are not initialized in the constructor are 0.
333
334 """
f9bc9e65
JF
335 _fields_ = [('type', ctypes.c_uint32),
336 ('size', ctypes.c_uint32),
337 ('config', ctypes.c_uint64),
338 ('sample_freq', ctypes.c_uint64),
339 ('sample_type', ctypes.c_uint64),
340 ('read_format', ctypes.c_uint64),
341 ('flags', ctypes.c_uint64),
342 ('wakeup_events', ctypes.c_uint32),
343 ('bp_type', ctypes.c_uint32),
344 ('bp_addr', ctypes.c_uint64),
345 ('bp_len', ctypes.c_uint64),
346 ]
347
348 def __init__(self):
349 super(self.__class__, self).__init__()
350 self.type = PERF_TYPE_TRACEPOINT
351 self.size = ctypes.sizeof(self)
352 self.read_format = PERF_FORMAT_GROUP
353
692c7f6d 354
f9bc9e65
JF
355PERF_TYPE_TRACEPOINT = 2
356PERF_FORMAT_GROUP = 1 << 3
357
692c7f6d 358
f9bc9e65 359class Group(object):
fabc7128
JF
360 """Represents a perf event group."""
361
f9bc9e65
JF
362 def __init__(self):
363 self.events = []
364
365 def add_event(self, event):
366 self.events.append(event)
367
368 def read(self):
fabc7128
JF
369 """Returns a dict with 'event name: value' for all events in the
370 group.
371
372 Values are read by reading from the file descriptor of the
373 event that is the group leader. See perf_event_open(2) for
374 details.
375
376 Read format for the used event configuration is:
377 struct read_format {
378 u64 nr; /* The number of events */
379 struct {
380 u64 value; /* The value of the event */
381 } values[nr];
382 };
383
384 """
f9bc9e65
JF
385 length = 8 * (1 + len(self.events))
386 read_format = 'xxxxxxxx' + 'Q' * len(self.events)
387 return dict(zip([event.name for event in self.events],
388 struct.unpack(read_format,
389 os.read(self.events[0].fd, length))))
390
692c7f6d 391
f9bc9e65 392class Event(object):
fabc7128 393 """Represents a performance event and manages its life cycle."""
f0cf040f
JF
394 def __init__(self, name, group, trace_cpu, trace_pid, trace_point,
395 trace_filter, trace_set='kvm'):
099a2dfc
SR
396 self.libc = ctypes.CDLL('libc.so.6', use_errno=True)
397 self.syscall = self.libc.syscall
f9bc9e65
JF
398 self.name = name
399 self.fd = None
c0e8c21e
SR
400 self._setup_event(group, trace_cpu, trace_pid, trace_point,
401 trace_filter, trace_set)
f0cf040f
JF
402
403 def __del__(self):
fabc7128
JF
404 """Closes the event's file descriptor.
405
406 As no python file object was created for the file descriptor,
407 python will not reference count the descriptor and will not
408 close it itself automatically, so we do it.
409
410 """
f0cf040f
JF
411 if self.fd:
412 os.close(self.fd)
f9bc9e65 413
c0e8c21e 414 def _perf_event_open(self, attr, pid, cpu, group_fd, flags):
099a2dfc
SR
415 """Wrapper for the sys_perf_evt_open() syscall.
416
417 Used to set up performance events, returns a file descriptor or -1
418 on error.
419
420 Attributes are:
421 - syscall number
422 - struct perf_event_attr *
423 - pid or -1 to monitor all pids
424 - cpu number or -1 to monitor all cpus
425 - The file descriptor of the group leader or -1 to create a group.
426 - flags
427
428 """
429 return self.syscall(ARCH.sc_perf_evt_open, ctypes.pointer(attr),
430 ctypes.c_int(pid), ctypes.c_int(cpu),
431 ctypes.c_int(group_fd), ctypes.c_long(flags))
432
c0e8c21e 433 def _setup_event_attribute(self, trace_set, trace_point):
fabc7128
JF
434 """Returns an initialized ctype perf_event_attr struct."""
435
f9bc9e65
JF
436 id_path = os.path.join(PATH_DEBUGFS_TRACING, 'events', trace_set,
437 trace_point, 'id')
438
439 event_attr = perf_event_attr()
440 event_attr.config = int(open(id_path).read())
441 return event_attr
442
c0e8c21e
SR
443 def _setup_event(self, group, trace_cpu, trace_pid, trace_point,
444 trace_filter, trace_set):
fabc7128
JF
445 """Sets up the perf event in Linux.
446
447 Issues the syscall to register the event in the kernel and
448 then sets the optional filter.
449
450 """
451
c0e8c21e 452 event_attr = self._setup_event_attribute(trace_set, trace_point)
f9bc9e65 453
fabc7128 454 # First event will be group leader.
f9bc9e65 455 group_leader = -1
fabc7128
JF
456
457 # All others have to pass the leader's descriptor instead.
f9bc9e65
JF
458 if group.events:
459 group_leader = group.events[0].fd
460
c0e8c21e
SR
461 fd = self._perf_event_open(event_attr, trace_pid,
462 trace_cpu, group_leader, 0)
f9bc9e65
JF
463 if fd == -1:
464 err = ctypes.get_errno()
465 raise OSError(err, os.strerror(err),
466 'while calling sys_perf_event_open().')
467
468 if trace_filter:
469 fcntl.ioctl(fd, ARCH.ioctl_numbers['SET_FILTER'],
470 trace_filter)
471
472 self.fd = fd
473
474 def enable(self):
fabc7128
JF
475 """Enables the trace event in the kernel.
476
477 Enabling the group leader makes reading counters from it and the
478 events under it possible.
479
480 """
f9bc9e65
JF
481 fcntl.ioctl(self.fd, ARCH.ioctl_numbers['ENABLE'], 0)
482
483 def disable(self):
fabc7128
JF
484 """Disables the trace event in the kernel.
485
486 Disabling the group leader makes reading all counters under it
487 impossible.
488
489 """
f9bc9e65
JF
490 fcntl.ioctl(self.fd, ARCH.ioctl_numbers['DISABLE'], 0)
491
492 def reset(self):
fabc7128 493 """Resets the count of the trace event in the kernel."""
f9bc9e65
JF
494 fcntl.ioctl(self.fd, ARCH.ioctl_numbers['RESET'], 0)
495
692c7f6d 496
099a2dfc
SR
497class Provider(object):
498 """Encapsulates functionalities used by all providers."""
18e8f410
SR
499 def __init__(self, pid):
500 self.child_events = False
501 self.pid = pid
502
099a2dfc
SR
503 @staticmethod
504 def is_field_wanted(fields_filter, field):
505 """Indicate whether field is valid according to fields_filter."""
b74faa93 506 if not fields_filter:
099a2dfc
SR
507 return True
508 return re.match(fields_filter, field) is not None
509
510 @staticmethod
511 def walkdir(path):
512 """Returns os.walk() data for specified directory.
513
514 As it is only a wrapper it returns the same 3-tuple of (dirpath,
515 dirnames, filenames).
516 """
517 return next(os.walk(path))
518
519
520class TracepointProvider(Provider):
fabc7128
JF
521 """Data provider for the stats class.
522
523 Manages the events/groups from which it acquires its data.
524
525 """
c469117d 526 def __init__(self, pid, fields_filter):
f9bc9e65 527 self.group_leaders = []
c0e8c21e 528 self.filters = self._get_filters()
c469117d 529 self.update_fields(fields_filter)
18e8f410 530 super(TracepointProvider, self).__init__(pid)
f9bc9e65 531
099a2dfc 532 @staticmethod
c0e8c21e 533 def _get_filters():
099a2dfc
SR
534 """Returns a dict of trace events, their filter ids and
535 the values that can be filtered.
536
537 Trace events can be filtered for special values by setting a
538 filter string via an ioctl. The string normally has the format
539 identifier==value. For each filter a new event will be created, to
540 be able to distinguish the events.
541
542 """
543 filters = {}
544 filters['kvm_userspace_exit'] = ('reason', USERSPACE_EXIT_REASONS)
545 if ARCH.exit_reasons:
546 filters['kvm_exit'] = ('exit_reason', ARCH.exit_reasons)
547 return filters
548
c0e8c21e 549 def _get_available_fields(self):
18e8f410 550 """Returns a list of available events of format 'event name(filter
fabc7128
JF
551 name)'.
552
553 All available events have directories under
554 /sys/kernel/debug/tracing/events/ which export information
555 about the specific event. Therefore, listing the dirs gives us
556 a list of all available events.
557
558 Some events like the vm exit reasons can be filtered for
559 specific values. To take account for that, the routine below
560 creates special fields with the following format:
561 event name(filter name)
562
563 """
f9bc9e65 564 path = os.path.join(PATH_DEBUGFS_TRACING, 'events', 'kvm')
099a2dfc 565 fields = self.walkdir(path)[1]
f9bc9e65
JF
566 extra = []
567 for field in fields:
568 if field in self.filters:
569 filter_name_, filter_dicts = self.filters[field]
570 for name in filter_dicts:
571 extra.append(field + '(' + name + ')')
572 fields += extra
573 return fields
574
c469117d
SR
575 def update_fields(self, fields_filter):
576 """Refresh fields, applying fields_filter"""
c0e8c21e 577 self.fields = [field for field in self._get_available_fields()
18e8f410
SR
578 if self.is_field_wanted(fields_filter, field) or
579 ARCH.tracepoint_is_child(field)]
099a2dfc
SR
580
581 @staticmethod
c0e8c21e 582 def _get_online_cpus():
099a2dfc
SR
583 """Returns a list of cpu id integers."""
584 def parse_int_list(list_string):
585 """Returns an int list from a string of comma separated integers and
586 integer ranges."""
587 integers = []
588 members = list_string.split(',')
589
590 for member in members:
591 if '-' not in member:
592 integers.append(int(member))
593 else:
594 int_range = member.split('-')
595 integers.extend(range(int(int_range[0]),
596 int(int_range[1]) + 1))
597
598 return integers
599
600 with open('/sys/devices/system/cpu/online') as cpu_list:
601 cpu_string = cpu_list.readline()
602 return parse_int_list(cpu_string)
c469117d 603
c0e8c21e 604 def _setup_traces(self):
fabc7128
JF
605 """Creates all event and group objects needed to be able to retrieve
606 data."""
c0e8c21e 607 fields = self._get_available_fields()
f0cf040f
JF
608 if self._pid > 0:
609 # Fetch list of all threads of the monitored pid, as qemu
610 # starts a thread for each vcpu.
611 path = os.path.join('/proc', str(self._pid), 'task')
099a2dfc 612 groupids = self.walkdir(path)[1]
f0cf040f 613 else:
c0e8c21e 614 groupids = self._get_online_cpus()
f9bc9e65
JF
615
616 # The constant is needed as a buffer for python libs, std
617 # streams and other files that the script opens.
a1836069 618 newlim = len(groupids) * len(fields) + 50
f9bc9e65
JF
619 try:
620 softlim_, hardlim = resource.getrlimit(resource.RLIMIT_NOFILE)
621
622 if hardlim < newlim:
623 # Now we need CAP_SYS_RESOURCE, to increase the hard limit.
624 resource.setrlimit(resource.RLIMIT_NOFILE, (newlim, newlim))
625 else:
626 # Raising the soft limit is sufficient.
627 resource.setrlimit(resource.RLIMIT_NOFILE, (newlim, hardlim))
628
629 except ValueError:
630 sys.exit("NOFILE rlimit could not be raised to {0}".format(newlim))
631
f0cf040f 632 for groupid in groupids:
f9bc9e65 633 group = Group()
a1836069 634 for name in fields:
f9bc9e65
JF
635 tracepoint = name
636 tracefilter = None
637 match = re.match(r'(.*)\((.*)\)', name)
638 if match:
639 tracepoint, sub = match.groups()
640 tracefilter = ('%s==%d\0' %
641 (self.filters[tracepoint][0],
642 self.filters[tracepoint][1][sub]))
643
f0cf040f
JF
644 # From perf_event_open(2):
645 # pid > 0 and cpu == -1
646 # This measures the specified process/thread on any CPU.
647 #
648 # pid == -1 and cpu >= 0
649 # This measures all processes/threads on the specified CPU.
650 trace_cpu = groupid if self._pid == 0 else -1
651 trace_pid = int(groupid) if self._pid != 0 else -1
652
f9bc9e65
JF
653 group.add_event(Event(name=name,
654 group=group,
f0cf040f
JF
655 trace_cpu=trace_cpu,
656 trace_pid=trace_pid,
f9bc9e65
JF
657 trace_point=tracepoint,
658 trace_filter=tracefilter))
f0cf040f 659
f9bc9e65
JF
660 self.group_leaders.append(group)
661
f9bc9e65
JF
662 @property
663 def fields(self):
664 return self._fields
665
666 @fields.setter
667 def fields(self, fields):
fabc7128 668 """Enables/disables the (un)wanted events"""
f9bc9e65
JF
669 self._fields = fields
670 for group in self.group_leaders:
671 for index, event in enumerate(group.events):
672 if event.name in fields:
673 event.reset()
674 event.enable()
675 else:
676 # Do not disable the group leader.
677 # It would disable all of its events.
678 if index != 0:
679 event.disable()
680
f0cf040f
JF
681 @property
682 def pid(self):
683 return self._pid
684
685 @pid.setter
686 def pid(self, pid):
fabc7128 687 """Changes the monitored pid by setting new traces."""
f0cf040f 688 self._pid = pid
fabc7128
JF
689 # The garbage collector will get rid of all Event/Group
690 # objects and open files after removing the references.
f0cf040f 691 self.group_leaders = []
c0e8c21e 692 self._setup_traces()
f0cf040f
JF
693 self.fields = self._fields
694
5c1954d2 695 def read(self, by_guest=0):
fabc7128 696 """Returns 'event name: current value' for all enabled events."""
f9bc9e65
JF
697 ret = defaultdict(int)
698 for group in self.group_leaders:
9cc5fbbb 699 for name, val in group.read().items():
18e8f410
SR
700 if name not in self._fields:
701 continue
702 parent = ARCH.tracepoint_is_child(name)
703 if parent:
704 name += ' ' + parent
705 ret[name] += val
f9bc9e65
JF
706 return ret
707
9f114a03
SR
708 def reset(self):
709 """Reset all field counters"""
710 for group in self.group_leaders:
711 for event in group.events:
712 event.reset()
713
692c7f6d 714
099a2dfc 715class DebugfsProvider(Provider):
fabc7128
JF
716 """Provides data from the files that KVM creates in the kvm debugfs
717 folder."""
ab7ef193 718 def __init__(self, pid, fields_filter, include_past):
c469117d 719 self.update_fields(fields_filter)
9f114a03 720 self._baseline = {}
f0cf040f 721 self.do_read = True
e0ba3876 722 self.paths = []
18e8f410 723 super(DebugfsProvider, self).__init__(pid)
ab7ef193 724 if include_past:
c0e8c21e 725 self._restore()
f9bc9e65 726
c0e8c21e 727 def _get_available_fields(self):
fabc7128
JF
728 """"Returns a list of available fields.
729
730 The fields are all available KVM debugfs files
731
732 """
099a2dfc 733 return self.walkdir(PATH_DEBUGFS_KVM)[2]
f9bc9e65 734
c469117d
SR
735 def update_fields(self, fields_filter):
736 """Refresh fields, applying fields_filter"""
c0e8c21e 737 self._fields = [field for field in self._get_available_fields()
18e8f410
SR
738 if self.is_field_wanted(fields_filter, field) or
739 ARCH.debugfs_is_child(field)]
c469117d 740
f9bc9e65
JF
741 @property
742 def fields(self):
743 return self._fields
744
745 @fields.setter
746 def fields(self, fields):
747 self._fields = fields
9f114a03 748 self.reset()
f9bc9e65 749
f0cf040f
JF
750 @property
751 def pid(self):
752 return self._pid
753
754 @pid.setter
755 def pid(self, pid):
c469117d 756 self._pid = pid
f0cf040f 757 if pid != 0:
099a2dfc 758 vms = self.walkdir(PATH_DEBUGFS_KVM)[1]
f0cf040f
JF
759 if len(vms) == 0:
760 self.do_read = False
761
58f33cfe 762 self.paths = list(filter(lambda x: "{}-".format(pid) in x, vms))
f0cf040f
JF
763
764 else:
9f114a03 765 self.paths = []
f0cf040f 766 self.do_read = True
9f114a03 767 self.reset()
f0cf040f 768
5c1954d2 769 def read(self, reset=0, by_guest=0):
ab7ef193
SR
770 """Returns a dict with format:'file name / field -> current value'.
771
772 Parameter 'reset':
773 0 plain read
774 1 reset field counts to 0
775 2 restore the original field counts
776
777 """
f0cf040f
JF
778 results = {}
779
780 # If no debugfs filtering support is available, then don't read.
781 if not self.do_read:
782 return results
783
9f114a03
SR
784 paths = self.paths
785 if self._pid == 0:
786 paths = []
787 for entry in os.walk(PATH_DEBUGFS_KVM):
788 for dir in entry[1]:
789 paths.append(dir)
790 for path in paths:
f0cf040f 791 for field in self._fields:
c0e8c21e 792 value = self._read_field(field, path)
9f114a03 793 key = path + field
ab7ef193 794 if reset == 1:
9f114a03 795 self._baseline[key] = value
ab7ef193
SR
796 if reset == 2:
797 self._baseline[key] = 0
9f114a03
SR
798 if self._baseline.get(key, -1) == -1:
799 self._baseline[key] = value
18e8f410
SR
800 parent = ARCH.debugfs_is_child(field)
801 if parent:
802 field = field + ' ' + parent
803 else:
804 if by_guest:
805 field = key.split('-')[0] # set 'field' to 'pid'
806 increment = value - self._baseline.get(key, 0)
807 if field in results:
808 results[field] += increment
5c1954d2
SR
809 else:
810 results[field] = increment
f0cf040f
JF
811
812 return results
813
c0e8c21e 814 def _read_field(self, field, path):
f0cf040f
JF
815 """Returns the value of a single field from a specific VM."""
816 try:
817 return int(open(os.path.join(PATH_DEBUGFS_KVM,
818 path,
819 field))
820 .read())
821 except IOError:
822 return 0
f9bc9e65 823
9f114a03
SR
824 def reset(self):
825 """Reset field counters"""
826 self._baseline = {}
827 self.read(1)
828
c0e8c21e 829 def _restore(self):
ab7ef193
SR
830 """Reset field counters"""
831 self._baseline = {}
832 self.read(2)
833
692c7f6d 834
006f1548
MH
835EventStat = namedtuple('EventStat', ['value', 'delta'])
836
837
f9bc9e65 838class Stats(object):
fabc7128
JF
839 """Manages the data providers and the data they provide.
840
841 It is used to set filters on the provider's data and collect all
842 provider data.
843
844 """
c469117d 845 def __init__(self, options):
c0e8c21e 846 self.providers = self._get_providers(options)
c469117d
SR
847 self._pid_filter = options.pid
848 self._fields_filter = options.fields
f9bc9e65 849 self.values = {}
18e8f410 850 self._child_events = False
f9bc9e65 851
c0e8c21e 852 def _get_providers(self, options):
099a2dfc
SR
853 """Returns a list of data providers depending on the passed options."""
854 providers = []
855
856 if options.debugfs:
ab7ef193
SR
857 providers.append(DebugfsProvider(options.pid, options.fields,
858 options.dbgfs_include_past))
099a2dfc
SR
859 if options.tracepoints or not providers:
860 providers.append(TracepointProvider(options.pid, options.fields))
861
862 return providers
863
c0e8c21e 864 def _update_provider_filters(self):
fabc7128 865 """Propagates fields filters to providers."""
f9bc9e65
JF
866 # As we reset the counters when updating the fields we can
867 # also clear the cache of old values.
868 self.values = {}
869 for provider in self.providers:
c469117d 870 provider.update_fields(self._fields_filter)
f0cf040f 871
9f114a03
SR
872 def reset(self):
873 self.values = {}
874 for provider in self.providers:
875 provider.reset()
876
f9bc9e65
JF
877 @property
878 def fields_filter(self):
879 return self._fields_filter
880
881 @fields_filter.setter
882 def fields_filter(self, fields_filter):
9f114a03
SR
883 if fields_filter != self._fields_filter:
884 self._fields_filter = fields_filter
c0e8c21e 885 self._update_provider_filters()
f9bc9e65 886
f0cf040f
JF
887 @property
888 def pid_filter(self):
889 return self._pid_filter
890
891 @pid_filter.setter
892 def pid_filter(self, pid):
9f114a03
SR
893 if pid != self._pid_filter:
894 self._pid_filter = pid
895 self.values = {}
c469117d
SR
896 for provider in self.providers:
897 provider.pid = self._pid_filter
f0cf040f 898
18e8f410
SR
899 @property
900 def child_events(self):
901 return self._child_events
902
903 @child_events.setter
904 def child_events(self, val):
905 self._child_events = val
906 for provider in self.providers:
907 provider.child_events = val
908
5c1954d2 909 def get(self, by_guest=0):
fabc7128 910 """Returns a dict with field -> (value, delta to last value) of all
18e8f410
SR
911 provider data.
912 Key formats:
913 * plain: 'key' is event name
914 * child-parent: 'key' is in format '<child> <parent>'
915 * pid: 'key' is the pid of the guest, and the record contains the
916 aggregated event data
917 These formats are generated by the providers, and handled in class TUI.
918 """
f9bc9e65 919 for provider in self.providers:
5c1954d2 920 new = provider.read(by_guest=by_guest)
18e8f410 921 for key in new:
006f1548 922 oldval = self.values.get(key, EventStat(0, 0)).value
f9bc9e65 923 newval = new.get(key, 0)
9f114a03 924 newdelta = newval - oldval
006f1548 925 self.values[key] = EventStat(newval, newdelta)
f9bc9e65
JF
926 return self.values
927
5c1954d2
SR
928 def toggle_display_guests(self, to_pid):
929 """Toggle between collection of stats by individual event and by
930 guest pid
931
932 Events reported by DebugfsProvider change when switching to/from
933 reading by guest values. Hence we have to remove the excess event
934 names from self.values.
935
936 """
937 if any(isinstance(ins, TracepointProvider) for ins in self.providers):
938 return 1
939 if to_pid:
940 for provider in self.providers:
941 if isinstance(provider, DebugfsProvider):
942 for key in provider.fields:
943 if key in self.values.keys():
944 del self.values[key]
945 else:
946 oldvals = self.values.copy()
947 for key in oldvals:
948 if key.isdigit():
949 del self.values[key]
950 # Update oldval (see get())
951 self.get(to_pid)
952 return 0
953
18e8f410 954
64eefad2 955DELAY_DEFAULT = 3.0
a24e85f6 956MAX_GUEST_NAME_LEN = 48
72187dfa 957MAX_REGEX_LEN = 44
6667ae8f 958SORT_DEFAULT = 0
f9bc9e65 959
692c7f6d 960
f9bc9e65 961class Tui(object):
fabc7128 962 """Instruments curses to draw a nice text ui."""
f9bc9e65
JF
963 def __init__(self, stats):
964 self.stats = stats
965 self.screen = None
64eefad2
SR
966 self._delay_initial = 0.25
967 self._delay_regular = DELAY_DEFAULT
6667ae8f 968 self._sorting = SORT_DEFAULT
5c1954d2 969 self._display_guests = 0
f9bc9e65
JF
970
971 def __enter__(self):
972 """Initialises curses for later use. Based on curses.wrapper
973 implementation from the Python standard library."""
974 self.screen = curses.initscr()
975 curses.noecho()
976 curses.cbreak()
977
978 # The try/catch works around a minor bit of
979 # over-conscientiousness in the curses module, the error
980 # return from C start_color() is ignorable.
981 try:
982 curses.start_color()
9fc0adfc 983 except curses.error:
f9bc9e65
JF
984 pass
985
a0b4e6a0
SR
986 # Hide cursor in extra statement as some monochrome terminals
987 # might support hiding but not colors.
988 try:
989 curses.curs_set(0)
990 except curses.error:
991 pass
992
f9bc9e65
JF
993 curses.use_default_colors()
994 return self
995
996 def __exit__(self, *exception):
773bffee 997 """Resets the terminal to its normal state. Based on curses.wrapper
f9bc9e65
JF
998 implementation from the Python standard library."""
999 if self.screen:
1000 self.screen.keypad(0)
1001 curses.echo()
1002 curses.nocbreak()
1003 curses.endwin()
1004
19e8e54f
SR
1005 @staticmethod
1006 def get_all_gnames():
865279c5
SR
1007 """Returns a list of (pid, gname) tuples of all running guests"""
1008 res = []
099a2dfc
SR
1009 try:
1010 child = subprocess.Popen(['ps', '-A', '--format', 'pid,args'],
1011 stdout=subprocess.PIPE)
1012 except:
1013 raise Exception
1014 for line in child.stdout:
9cc5fbbb 1015 line = line.decode(ENCODING).lstrip().split(' ', 1)
099a2dfc
SR
1016 # perform a sanity check before calling the more expensive
1017 # function to possibly extract the guest name
865279c5 1018 if ' -name ' in line[1]:
19e8e54f 1019 res.append((line[0], Tui.get_gname_from_pid(line[0])))
099a2dfc
SR
1020 child.stdout.close()
1021
865279c5
SR
1022 return res
1023
c0e8c21e 1024 def _print_all_gnames(self, row):
865279c5
SR
1025 """Print a list of all running guests along with their pids."""
1026 self.screen.addstr(row, 2, '%8s %-60s' %
1027 ('Pid', 'Guest Name (fuzzy list, might be '
1028 'inaccurate!)'),
1029 curses.A_UNDERLINE)
1030 row += 1
1031 try:
1032 for line in self.get_all_gnames():
1033 self.screen.addstr(row, 2, '%8s %-60s' % (line[0], line[1]))
1034 row += 1
1035 if row >= self.screen.getmaxyx()[0]:
1036 break
1037 except Exception:
1038 self.screen.addstr(row + 1, 2, 'Not available')
1039
19e8e54f
SR
1040 @staticmethod
1041 def get_pid_from_gname(gname):
865279c5
SR
1042 """Fuzzy function to convert guest name to QEMU process pid.
1043
1044 Returns a list of potential pids, can be empty if no match found.
1045 Throws an exception on processing errors.
1046
1047 """
1048 pids = []
19e8e54f 1049 for line in Tui.get_all_gnames():
865279c5
SR
1050 if gname == line[1]:
1051 pids.append(int(line[0]))
1052
099a2dfc
SR
1053 return pids
1054
1055 @staticmethod
1056 def get_gname_from_pid(pid):
1057 """Returns the guest name for a QEMU process pid.
1058
1059 Extracts the guest name from the QEMU comma line by processing the
1060 '-name' option. Will also handle names specified out of sequence.
1061
1062 """
1063 name = ''
1064 try:
1065 line = open('/proc/{}/cmdline'
9cc5fbbb 1066 .format(pid), 'r').read().split('\0')
099a2dfc
SR
1067 parms = line[line.index('-name') + 1].split(',')
1068 while '' in parms:
1069 # commas are escaped (i.e. ',,'), hence e.g. 'foo,bar' results
1070 # in # ['foo', '', 'bar'], which we revert here
1071 idx = parms.index('')
1072 parms[idx - 1] += ',' + parms[idx + 1]
1073 del parms[idx:idx+2]
1074 # the '-name' switch allows for two ways to specify the guest name,
1075 # where the plain name overrides the name specified via 'guest='
1076 for arg in parms:
1077 if '=' not in arg:
1078 name = arg
1079 break
1080 if arg[:6] == 'guest=':
1081 name = arg[6:]
1082 except (ValueError, IOError, IndexError):
1083 pass
1084
1085 return name
1086
c0e8c21e 1087 def _update_pid(self, pid):
fabc7128 1088 """Propagates pid selection to stats object."""
516f1190
SR
1089 self.screen.addstr(4, 1, 'Updating pid filter...')
1090 self.screen.refresh()
f0cf040f
JF
1091 self.stats.pid_filter = pid
1092
c0e8c21e 1093 def _refresh_header(self, pid=None):
184b2d23
SR
1094 """Refreshes the header."""
1095 if pid is None:
1096 pid = self.stats.pid_filter
f9bc9e65 1097 self.screen.erase()
099a2dfc 1098 gname = self.get_gname_from_pid(pid)
a24e85f6
SR
1099 if gname:
1100 gname = ('({})'.format(gname[:MAX_GUEST_NAME_LEN] + '...'
1101 if len(gname) > MAX_GUEST_NAME_LEN
1102 else gname))
184b2d23 1103 if pid > 0:
a24e85f6
SR
1104 self.screen.addstr(0, 0, 'kvm statistics - pid {0} {1}'
1105 .format(pid, gname), curses.A_BOLD)
f0cf040f
JF
1106 else:
1107 self.screen.addstr(0, 0, 'kvm statistics - summary', curses.A_BOLD)
18e8f410 1108 if self.stats.fields_filter:
72187dfa
SR
1109 regex = self.stats.fields_filter
1110 if len(regex) > MAX_REGEX_LEN:
1111 regex = regex[:MAX_REGEX_LEN] + '...'
1112 self.screen.addstr(1, 17, 'regex filter: {0}'.format(regex))
5c1954d2
SR
1113 if self._display_guests:
1114 col_name = 'Guest Name'
1115 else:
1116 col_name = 'Event'
38e89c37 1117 self.screen.addstr(2, 1, '%-40s %10s%7s %8s' %
5c1954d2 1118 (col_name, 'Total', '%Total', 'CurAvg/s'),
f6d75310 1119 curses.A_STANDOUT)
184b2d23
SR
1120 self.screen.addstr(4, 1, 'Collecting data...')
1121 self.screen.refresh()
1122
c0e8c21e 1123 def _refresh_body(self, sleeptime):
df72ecfc
SR
1124 def insert_child(sorted_items, child, values, parent):
1125 num = len(sorted_items)
1126 for i in range(0, num):
1127 # only add child if parent is present
1128 if parent.startswith(sorted_items[i][0]):
1129 sorted_items.insert(i + 1, (' ' + child, values))
1130
1131 def get_sorted_events(self, stats):
1132 """ separate parent and child events """
1133 if self._sorting == SORT_DEFAULT:
6ade1ae8 1134 def sortkey(pair):
df72ecfc 1135 # sort by (delta value, overall value)
6ade1ae8 1136 v = pair[1]
df72ecfc
SR
1137 return (v.delta, v.value)
1138 else:
6ade1ae8 1139 def sortkey(pair):
df72ecfc 1140 # sort by overall value
6ade1ae8 1141 v = pair[1]
df72ecfc
SR
1142 return v.value
1143
1144 childs = []
1145 sorted_items = []
1146 # we can't rule out child events to appear prior to parents even
1147 # when sorted - separate out all children first, and add in later
1148 for key, values in sorted(stats.items(), key=sortkey,
1149 reverse=True):
1150 if values == (0, 0):
1151 continue
1152 if key.find(' ') != -1:
1153 if not self.stats.child_events:
1154 continue
1155 childs.insert(0, (key, values))
1156 else:
1157 sorted_items.append((key, values))
1158 if self.stats.child_events:
1159 for key, values in childs:
1160 (child, parent) = key.split(' ')
1161 insert_child(sorted_items, child, values, parent)
1162
1163 return sorted_items
1164
f9bc9e65 1165 row = 3
184b2d23
SR
1166 self.screen.move(row, 0)
1167 self.screen.clrtobot()
5c1954d2 1168 stats = self.stats.get(self._display_guests)
e55fe3cc 1169 total = 0.
3df33a0f 1170 ctotal = 0.
0eb57800 1171 for key, values in stats.items():
18e8f410
SR
1172 if self._display_guests:
1173 if self.get_gname_from_pid(key):
1174 total += values.value
1175 continue
1176 if not key.find(' ') != -1:
0eb57800 1177 total += values.value
3df33a0f
SR
1178 else:
1179 ctotal += values.value
1180 if total == 0.:
1181 # we don't have any fields, or all non-child events are filtered
1182 total = ctotal
0eb57800 1183
18e8f410 1184 # print events
cf656c76 1185 tavg = 0
df72ecfc
SR
1186 tcur = 0
1187 for key, values in get_sorted_events(self, stats):
1188 if row >= self.screen.getmaxyx()[0] - 1 or values == (0, 0):
f9bc9e65 1189 break
df72ecfc
SR
1190 if self._display_guests:
1191 key = self.get_gname_from_pid(key)
1192 if not key:
1193 continue
1194 cur = int(round(values.delta / sleeptime)) if values.delta else ''
1195 if key[0] != ' ':
1196 if values.delta:
1197 tcur += values.delta
1198 ptotal = values.value
1199 ltotal = total
1200 else:
1201 ltotal = ptotal
1202 self.screen.addstr(row, 1, '%-40s %10d%7.1f %8s' % (key,
1203 values.value,
1204 values.value * 100 / float(ltotal), cur))
f9bc9e65 1205 row += 1
57253937
SR
1206 if row == 3:
1207 self.screen.addstr(4, 1, 'No matching events reported yet')
6789af03 1208 if row > 4:
df72ecfc 1209 tavg = int(round(tcur / sleeptime)) if tcur > 0 else ''
cf656c76 1210 self.screen.addstr(row, 1, '%-40s %10d %8s' %
df72ecfc 1211 ('Total', total, tavg), curses.A_BOLD)
f9bc9e65
JF
1212 self.screen.refresh()
1213
c0e8c21e 1214 def _show_msg(self, text):
5c1954d2
SR
1215 """Display message centered text and exit on key press"""
1216 hint = 'Press any key to continue'
1217 curses.cbreak()
1218 self.screen.erase()
1219 (x, term_width) = self.screen.getmaxyx()
1220 row = 2
1221 for line in text:
58f33cfe 1222 start = (term_width - len(line)) // 2
5c1954d2
SR
1223 self.screen.addstr(row, start, line)
1224 row += 1
58f33cfe 1225 self.screen.addstr(row + 1, (term_width - len(hint)) // 2, hint,
5c1954d2
SR
1226 curses.A_STANDOUT)
1227 self.screen.getkey()
1228
c0e8c21e 1229 def _show_help_interactive(self):
1fdea7b2 1230 """Display help with list of interactive commands"""
5c1954d2
SR
1231 msg = (' b toggle events by guests (debugfs only, honors'
1232 ' filters)',
1233 ' c clear filter',
1fdea7b2 1234 ' f filter by regular expression',
516f1190 1235 ' g filter by guest name/PID',
1fdea7b2 1236 ' h display interactive commands reference',
6667ae8f 1237 ' o toggle sorting order (Total vs CurAvg/s)',
516f1190 1238 ' p filter by guest name/PID',
1fdea7b2
SR
1239 ' q quit',
1240 ' r reset stats',
64eefad2 1241 ' s set update interval',
1fdea7b2
SR
1242 ' x toggle reporting of stats for individual child trace'
1243 ' events',
1244 'Any other key refreshes statistics immediately')
1245 curses.cbreak()
1246 self.screen.erase()
1247 self.screen.addstr(0, 0, "Interactive commands reference",
1248 curses.A_BOLD)
1249 self.screen.addstr(2, 0, "Press any key to exit", curses.A_STANDOUT)
1250 row = 4
1251 for line in msg:
1252 self.screen.addstr(row, 0, line)
1253 row += 1
1254 self.screen.getkey()
c0e8c21e 1255 self._refresh_header()
1fdea7b2 1256
c0e8c21e 1257 def _show_filter_selection(self):
fabc7128
JF
1258 """Draws filter selection mask.
1259
1260 Asks for a valid regex and sets the fields filter accordingly.
1261
1262 """
1cd8bfb1 1263 msg = ''
f9bc9e65
JF
1264 while True:
1265 self.screen.erase()
1266 self.screen.addstr(0, 0,
1267 "Show statistics for events matching a regex.",
1268 curses.A_BOLD)
1269 self.screen.addstr(2, 0,
1270 "Current regex: {0}"
1271 .format(self.stats.fields_filter))
1cd8bfb1 1272 self.screen.addstr(5, 0, msg)
f9bc9e65
JF
1273 self.screen.addstr(3, 0, "New regex: ")
1274 curses.echo()
9cc5fbbb 1275 regex = self.screen.getstr().decode(ENCODING)
f9bc9e65
JF
1276 curses.noecho()
1277 if len(regex) == 0:
18e8f410 1278 self.stats.fields_filter = ''
c0e8c21e 1279 self._refresh_header()
f9bc9e65
JF
1280 return
1281 try:
1282 re.compile(regex)
1283 self.stats.fields_filter = regex
c0e8c21e 1284 self._refresh_header()
f9bc9e65
JF
1285 return
1286 except re.error:
1cd8bfb1 1287 msg = '"' + regex + '": Not a valid regular expression'
f9bc9e65
JF
1288 continue
1289
c0e8c21e 1290 def _show_set_update_interval(self):
64eefad2
SR
1291 """Draws update interval selection mask."""
1292 msg = ''
1293 while True:
1294 self.screen.erase()
1295 self.screen.addstr(0, 0, 'Set update interval (defaults to %fs).' %
1296 DELAY_DEFAULT, curses.A_BOLD)
1297 self.screen.addstr(4, 0, msg)
1298 self.screen.addstr(2, 0, 'Change delay from %.1fs to ' %
1299 self._delay_regular)
1300 curses.echo()
9cc5fbbb 1301 val = self.screen.getstr().decode(ENCODING)
64eefad2
SR
1302 curses.noecho()
1303
1304 try:
1305 if len(val) > 0:
1306 delay = float(val)
1307 if delay < 0.1:
1308 msg = '"' + str(val) + '": Value must be >=0.1'
1309 continue
1310 if delay > 25.5:
1311 msg = '"' + str(val) + '": Value must be <=25.5'
1312 continue
1313 else:
1314 delay = DELAY_DEFAULT
1315 self._delay_regular = delay
1316 break
1317
1318 except ValueError:
1319 msg = '"' + str(val) + '": Invalid value'
c0e8c21e 1320 self._refresh_header()
64eefad2 1321
516f1190 1322 def _show_vm_selection_by_guest(self):
f9ff1087
SR
1323 """Draws guest selection mask.
1324
516f1190 1325 Asks for a guest name or pid until a valid guest name or '' is entered.
f9ff1087
SR
1326
1327 """
1328 msg = ''
1329 while True:
1330 self.screen.erase()
1331 self.screen.addstr(0, 0,
516f1190 1332 'Show statistics for specific guest or pid.',
f9ff1087
SR
1333 curses.A_BOLD)
1334 self.screen.addstr(1, 0,
1335 'This might limit the shown data to the trace '
1336 'statistics.')
1337 self.screen.addstr(5, 0, msg)
c0e8c21e 1338 self._print_all_gnames(7)
f9ff1087 1339 curses.echo()
516f1190
SR
1340 curses.curs_set(1)
1341 self.screen.addstr(3, 0, "Guest or pid [ENTER exits]: ")
1342 guest = self.screen.getstr().decode(ENCODING)
f9ff1087
SR
1343 curses.noecho()
1344
516f1190
SR
1345 pid = 0
1346 if not guest or guest == '0':
f9ff1087 1347 break
516f1190
SR
1348 if guest.isdigit():
1349 if not os.path.isdir(os.path.join('/proc/', guest)):
1350 msg = '"' + guest + '": Not a running process'
f9ff1087 1351 continue
516f1190 1352 pid = int(guest)
f9ff1087 1353 break
516f1190
SR
1354 pids = []
1355 try:
1356 pids = self.get_pid_from_gname(guest)
1357 except:
1358 msg = '"' + guest + '": Internal error while searching, ' \
1359 'use pid filter instead'
1360 continue
1361 if len(pids) == 0:
1362 msg = '"' + guest + '": Not an active guest'
1363 continue
1364 if len(pids) > 1:
1365 msg = '"' + guest + '": Multiple matches found, use pid ' \
1366 'filter instead'
1367 continue
1368 pid = pids[0]
1369 break
1370 curses.curs_set(0)
1371 self._refresh_header(pid)
1372 self._update_pid(pid)
f9ff1087 1373
f9bc9e65 1374 def show_stats(self):
fabc7128 1375 """Refreshes the screen and processes user input."""
64eefad2 1376 sleeptime = self._delay_initial
c0e8c21e 1377 self._refresh_header()
124c2fc9 1378 start = 0.0 # result based on init value never appears on screen
f9bc9e65 1379 while True:
c0e8c21e 1380 self._refresh_body(time.time() - start)
f9bc9e65 1381 curses.halfdelay(int(sleeptime * 10))
124c2fc9 1382 start = time.time()
64eefad2 1383 sleeptime = self._delay_regular
f9bc9e65
JF
1384 try:
1385 char = self.screen.getkey()
5c1954d2
SR
1386 if char == 'b':
1387 self._display_guests = not self._display_guests
1388 if self.stats.toggle_display_guests(self._display_guests):
c0e8c21e
SR
1389 self._show_msg(['Command not available with '
1390 'tracepoints enabled', 'Restart with '
1391 'debugfs only (see option \'-d\') and '
1392 'try again!'])
5c1954d2 1393 self._display_guests = not self._display_guests
c0e8c21e 1394 self._refresh_header()
4443084f 1395 if char == 'c':
18e8f410 1396 self.stats.fields_filter = ''
c0e8c21e
SR
1397 self._refresh_header(0)
1398 self._update_pid(0)
f9bc9e65 1399 if char == 'f':
62d1b6cc 1400 curses.curs_set(1)
c0e8c21e 1401 self._show_filter_selection()
62d1b6cc 1402 curses.curs_set(0)
64eefad2 1403 sleeptime = self._delay_initial
516f1190
SR
1404 if char == 'g' or char == 'p':
1405 self._show_vm_selection_by_guest()
64eefad2 1406 sleeptime = self._delay_initial
1fdea7b2 1407 if char == 'h':
c0e8c21e 1408 self._show_help_interactive()
6667ae8f
SR
1409 if char == 'o':
1410 self._sorting = not self._sorting
1fdea7b2
SR
1411 if char == 'q':
1412 break
9f114a03 1413 if char == 'r':
9f114a03 1414 self.stats.reset()
64eefad2
SR
1415 if char == 's':
1416 curses.curs_set(1)
c0e8c21e 1417 self._show_set_update_interval()
64eefad2
SR
1418 curses.curs_set(0)
1419 sleeptime = self._delay_initial
1fdea7b2 1420 if char == 'x':
18e8f410 1421 self.stats.child_events = not self.stats.child_events
f9bc9e65
JF
1422 except KeyboardInterrupt:
1423 break
1424 except curses.error:
1425 continue
1426
692c7f6d 1427
f9bc9e65 1428def batch(stats):
fabc7128 1429 """Prints statistics in a key, value format."""
dadf1e78
SR
1430 try:
1431 s = stats.get()
1432 time.sleep(1)
1433 s = stats.get()
0eb57800 1434 for key, values in sorted(s.items()):
18e8f410
SR
1435 print('%-42s%10d%10d' % (key.split(' ')[0], values.value,
1436 values.delta))
dadf1e78
SR
1437 except KeyboardInterrupt:
1438 pass
f9bc9e65 1439
692c7f6d 1440
f9bc9e65 1441def log(stats):
fabc7128 1442 """Prints statistics as reiterating key block, multiple value blocks."""
9cc5fbbb 1443 keys = sorted(stats.get().keys())
692c7f6d 1444
f9bc9e65 1445 def banner():
0eb57800 1446 for key in keys:
18e8f410 1447 print(key.split(' ')[0], end=' ')
9cc5fbbb 1448 print()
692c7f6d 1449
f9bc9e65
JF
1450 def statline():
1451 s = stats.get()
0eb57800
MH
1452 for key in keys:
1453 print(' %9d' % s[key].delta, end=' ')
9cc5fbbb 1454 print()
f9bc9e65
JF
1455 line = 0
1456 banner_repeat = 20
1457 while True:
dadf1e78
SR
1458 try:
1459 time.sleep(1)
1460 if line % banner_repeat == 0:
1461 banner()
1462 statline()
1463 line += 1
1464 except KeyboardInterrupt:
1465 break
f9bc9e65 1466
692c7f6d 1467
f9bc9e65 1468def get_options():
fabc7128 1469 """Returns processed program arguments."""
f9bc9e65
JF
1470 description_text = """
1471This script displays various statistics about VMs running under KVM.
1472The statistics are gathered from the KVM debugfs entries and / or the
1473currently available perf traces.
1474
1475The monitoring takes additional cpu cycles and might affect the VM's
1476performance.
1477
1478Requirements:
1479- Access to:
efcb5219
LM
1480 %s
1481 %s/events/*
f9bc9e65
JF
1482 /proc/pid/task
1483- /proc/sys/kernel/perf_event_paranoid < 1 if user has no
1484 CAP_SYS_ADMIN and perf events are used.
1485- CAP_SYS_RESOURCE if the hard limit is not high enough to allow
1486 the large number of files that are possibly opened.
1eaa2f90
SR
1487
1488Interactive Commands:
5c1954d2 1489 b toggle events by guests (debugfs only, honors filters)
4443084f 1490 c clear filter
1eaa2f90 1491 f filter by regular expression
f9ff1087 1492 g filter by guest name
1fdea7b2 1493 h display interactive commands reference
6667ae8f 1494 o toggle sorting order (Total vs CurAvg/s)
1eaa2f90
SR
1495 p filter by PID
1496 q quit
9f114a03 1497 r reset stats
64eefad2 1498 s set update interval
1fdea7b2 1499 x toggle reporting of stats for individual child trace events
1eaa2f90 1500Press any other key to refresh statistics immediately.
efcb5219 1501""" % (PATH_DEBUGFS_KVM, PATH_DEBUGFS_TRACING)
f9bc9e65
JF
1502
1503 class PlainHelpFormatter(optparse.IndentedHelpFormatter):
1504 def format_description(self, description):
1505 if description:
1506 return description + "\n"
1507 else:
1508 return ""
1509
f9ff1087
SR
1510 def cb_guest_to_pid(option, opt, val, parser):
1511 try:
099a2dfc 1512 pids = Tui.get_pid_from_gname(val)
f9ff1087 1513 except:
822cfe3e
SR
1514 sys.exit('Error while searching for guest "{}". Use "-p" to '
1515 'specify a pid instead?'.format(val))
f9ff1087 1516 if len(pids) == 0:
822cfe3e 1517 sys.exit('Error: No guest by the name "{}" found'.format(val))
f9ff1087 1518 if len(pids) > 1:
822cfe3e
SR
1519 sys.exit('Error: Multiple processes found (pids: {}). Use "-p" '
1520 'to specify the desired pid'.format(" ".join(pids)))
f9ff1087
SR
1521 parser.values.pid = pids[0]
1522
f9bc9e65
JF
1523 optparser = optparse.OptionParser(description=description_text,
1524 formatter=PlainHelpFormatter())
1525 optparser.add_option('-1', '--once', '--batch',
1526 action='store_true',
1527 default=False,
1528 dest='once',
1529 help='run in batch mode for one second',
1530 )
ab7ef193
SR
1531 optparser.add_option('-i', '--debugfs-include-past',
1532 action='store_true',
1533 default=False,
1534 dest='dbgfs_include_past',
1535 help='include all available data on past events for '
1536 'debugfs',
1537 )
f9bc9e65
JF
1538 optparser.add_option('-l', '--log',
1539 action='store_true',
1540 default=False,
1541 dest='log',
1542 help='run in logging mode (like vmstat)',
1543 )
1544 optparser.add_option('-t', '--tracepoints',
1545 action='store_true',
1546 default=False,
1547 dest='tracepoints',
1548 help='retrieve statistics from tracepoints',
1549 )
1550 optparser.add_option('-d', '--debugfs',
1551 action='store_true',
1552 default=False,
1553 dest='debugfs',
1554 help='retrieve statistics from debugfs',
1555 )
1556 optparser.add_option('-f', '--fields',
1557 action='store',
18e8f410 1558 default='',
f9bc9e65 1559 dest='fields',
67fbcd62
LM
1560 help='''fields to display (regex)
1561 "-f help" for a list of available events''',
f9bc9e65 1562 )
f0cf040f 1563 optparser.add_option('-p', '--pid',
e0ba3876
SR
1564 action='store',
1565 default=0,
1566 type='int',
1567 dest='pid',
1568 help='restrict statistics to pid',
1569 )
f9ff1087
SR
1570 optparser.add_option('-g', '--guest',
1571 action='callback',
1572 type='string',
1573 dest='pid',
1574 metavar='GUEST',
1575 help='restrict statistics to guest by name',
1576 callback=cb_guest_to_pid,
1577 )
73fab6ff
SR
1578 options, unkn = optparser.parse_args(sys.argv)
1579 if len(unkn) != 1:
1580 sys.exit('Error: Extra argument(s): ' + ' '.join(unkn[1:]))
08e20a63
SR
1581 try:
1582 # verify that we were passed a valid regex up front
1583 re.compile(options.fields)
1584 except re.error:
1585 sys.exit('Error: "' + options.fields + '" is not a valid regular '
1586 'expression')
1587
f9bc9e65
JF
1588 return options
1589
692c7f6d 1590
f9bc9e65 1591def check_access(options):
fabc7128 1592 """Exits if the current user can't access all needed directories."""
e0ba3876
SR
1593 if not os.path.exists(PATH_DEBUGFS_TRACING) and (options.tracepoints or
1594 not options.debugfs):
f9bc9e65
JF
1595 sys.stderr.write("Please enable CONFIG_TRACING in your kernel "
1596 "when using the option -t (default).\n"
1597 "If it is enabled, make {0} readable by the "
1598 "current user.\n"
1599 .format(PATH_DEBUGFS_TRACING))
1600 if options.tracepoints:
1601 sys.exit(1)
1602
1603 sys.stderr.write("Falling back to debugfs statistics!\n")
1604 options.debugfs = True
e0ba3876 1605 time.sleep(5)
f9bc9e65
JF
1606
1607 return options
1608
692c7f6d 1609
1fd6a708
SR
1610def assign_globals():
1611 global PATH_DEBUGFS_KVM
1612 global PATH_DEBUGFS_TRACING
1613
1614 debugfs = ''
0866c31b 1615 for line in open('/proc/mounts'):
1fd6a708
SR
1616 if line.split(' ')[0] == 'debugfs':
1617 debugfs = line.split(' ')[1]
1618 break
1619 if debugfs == '':
1620 sys.stderr.write("Please make sure that CONFIG_DEBUG_FS is enabled in "
1621 "your kernel, mounted and\nreadable by the current "
1622 "user:\n"
1623 "('mount -t debugfs debugfs /sys/kernel/debug')\n")
1624 sys.exit(1)
1625
1626 PATH_DEBUGFS_KVM = os.path.join(debugfs, 'kvm')
1627 PATH_DEBUGFS_TRACING = os.path.join(debugfs, 'tracing')
1628
1629 if not os.path.exists(PATH_DEBUGFS_KVM):
1630 sys.stderr.write("Please make sure that CONFIG_KVM is enabled in "
1631 "your kernel and that the modules are loaded.\n")
1632 sys.exit(1)
1633
1634
f9bc9e65 1635def main():
1fd6a708 1636 assign_globals()
f9bc9e65
JF
1637 options = get_options()
1638 options = check_access(options)
f0cf040f
JF
1639
1640 if (options.pid > 0 and
1641 not os.path.isdir(os.path.join('/proc/',
1642 str(options.pid)))):
1643 sys.stderr.write('Did you use a (unsupported) tid instead of a pid?\n')
1644 sys.exit('Specified pid does not exist.')
1645
c469117d 1646 stats = Stats(options)
f9bc9e65 1647
aa12f594 1648 if options.fields == 'help':
b74faa93 1649 stats.fields_filter = None
aa12f594
SR
1650 event_list = []
1651 for key in stats.get().keys():
1652 event_list.append(key.split('(', 1)[0])
1653 sys.stdout.write(' ' + '\n '.join(sorted(set(event_list))) + '\n')
1654 sys.exit(0)
67fbcd62 1655
f9bc9e65
JF
1656 if options.log:
1657 log(stats)
1658 elif not options.once:
1659 with Tui(stats) as tui:
1660 tui.show_stats()
1661 else:
1662 batch(stats)
1663
1664if __name__ == "__main__":
1665 main()