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