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