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