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