blob: 470ca08d0649311e49af13e7a66047139c95305c [file] [log] [blame]
Jan Kiszka626c4272011-10-07 09:37:49 +02001#!/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.
13
14import curses
Michael Ellerman47253982014-06-17 17:54:35 +100015import sys, os, time, optparse, ctypes
Jan Kiszka626c4272011-10-07 09:37:49 +020016
17class DebugfsProvider(object):
18 def __init__(self):
19 self.base = '/sys/kernel/debug/kvm'
20 self._fields = os.listdir(self.base)
21 def fields(self):
22 return self._fields
23 def select(self, fields):
24 self._fields = fields
25 def read(self):
26 def val(key):
27 return int(file(self.base + '/' + key).read())
28 return dict([(key, val(key)) for key in self._fields])
29
30vmx_exit_reasons = {
31 0: 'EXCEPTION_NMI',
32 1: 'EXTERNAL_INTERRUPT',
33 2: 'TRIPLE_FAULT',
34 7: 'PENDING_INTERRUPT',
35 8: 'NMI_WINDOW',
36 9: 'TASK_SWITCH',
37 10: 'CPUID',
38 12: 'HLT',
39 14: 'INVLPG',
40 15: 'RDPMC',
41 16: 'RDTSC',
42 18: 'VMCALL',
43 19: 'VMCLEAR',
44 20: 'VMLAUNCH',
45 21: 'VMPTRLD',
46 22: 'VMPTRST',
47 23: 'VMREAD',
48 24: 'VMRESUME',
49 25: 'VMWRITE',
50 26: 'VMOFF',
51 27: 'VMON',
52 28: 'CR_ACCESS',
53 29: 'DR_ACCESS',
54 30: 'IO_INSTRUCTION',
55 31: 'MSR_READ',
56 32: 'MSR_WRITE',
57 33: 'INVALID_STATE',
58 36: 'MWAIT_INSTRUCTION',
59 39: 'MONITOR_INSTRUCTION',
60 40: 'PAUSE_INSTRUCTION',
61 41: 'MCE_DURING_VMENTRY',
62 43: 'TPR_BELOW_THRESHOLD',
63 44: 'APIC_ACCESS',
64 48: 'EPT_VIOLATION',
65 49: 'EPT_MISCONFIG',
66 54: 'WBINVD',
67 55: 'XSETBV',
68}
69
70svm_exit_reasons = {
71 0x000: 'READ_CR0',
72 0x003: 'READ_CR3',
73 0x004: 'READ_CR4',
74 0x008: 'READ_CR8',
75 0x010: 'WRITE_CR0',
76 0x013: 'WRITE_CR3',
77 0x014: 'WRITE_CR4',
78 0x018: 'WRITE_CR8',
79 0x020: 'READ_DR0',
80 0x021: 'READ_DR1',
81 0x022: 'READ_DR2',
82 0x023: 'READ_DR3',
83 0x024: 'READ_DR4',
84 0x025: 'READ_DR5',
85 0x026: 'READ_DR6',
86 0x027: 'READ_DR7',
87 0x030: 'WRITE_DR0',
88 0x031: 'WRITE_DR1',
89 0x032: 'WRITE_DR2',
90 0x033: 'WRITE_DR3',
91 0x034: 'WRITE_DR4',
92 0x035: 'WRITE_DR5',
93 0x036: 'WRITE_DR6',
94 0x037: 'WRITE_DR7',
95 0x040: 'EXCP_BASE',
96 0x060: 'INTR',
97 0x061: 'NMI',
98 0x062: 'SMI',
99 0x063: 'INIT',
100 0x064: 'VINTR',
101 0x065: 'CR0_SEL_WRITE',
102 0x066: 'IDTR_READ',
103 0x067: 'GDTR_READ',
104 0x068: 'LDTR_READ',
105 0x069: 'TR_READ',
106 0x06a: 'IDTR_WRITE',
107 0x06b: 'GDTR_WRITE',
108 0x06c: 'LDTR_WRITE',
109 0x06d: 'TR_WRITE',
110 0x06e: 'RDTSC',
111 0x06f: 'RDPMC',
112 0x070: 'PUSHF',
113 0x071: 'POPF',
114 0x072: 'CPUID',
115 0x073: 'RSM',
116 0x074: 'IRET',
117 0x075: 'SWINT',
118 0x076: 'INVD',
119 0x077: 'PAUSE',
120 0x078: 'HLT',
121 0x079: 'INVLPG',
122 0x07a: 'INVLPGA',
123 0x07b: 'IOIO',
124 0x07c: 'MSR',
125 0x07d: 'TASK_SWITCH',
126 0x07e: 'FERR_FREEZE',
127 0x07f: 'SHUTDOWN',
128 0x080: 'VMRUN',
129 0x081: 'VMMCALL',
130 0x082: 'VMLOAD',
131 0x083: 'VMSAVE',
132 0x084: 'STGI',
133 0x085: 'CLGI',
134 0x086: 'SKINIT',
135 0x087: 'RDTSCP',
136 0x088: 'ICEBP',
137 0x089: 'WBINVD',
138 0x08a: 'MONITOR',
139 0x08b: 'MWAIT',
140 0x08c: 'MWAIT_COND',
141 0x400: 'NPF',
142}
143
Michael Ellerman27d318a2014-06-17 17:54:31 +1000144# From include/uapi/linux/kvm.h, KVM_EXIT_xxx
145userspace_exit_reasons = {
146 0: 'UNKNOWN',
147 1: 'EXCEPTION',
148 2: 'IO',
149 3: 'HYPERCALL',
150 4: 'DEBUG',
151 5: 'HLT',
152 6: 'MMIO',
153 7: 'IRQ_WINDOW_OPEN',
154 8: 'SHUTDOWN',
155 9: 'FAIL_ENTRY',
156 10: 'INTR',
157 11: 'SET_TPR',
158 12: 'TPR_ACCESS',
159 13: 'S390_SIEIC',
160 14: 'S390_RESET',
161 15: 'DCR',
162 16: 'NMI',
163 17: 'INTERNAL_ERROR',
164 18: 'OSI',
165 19: 'PAPR_HCALL',
166 20: 'S390_UCONTROL',
167 21: 'WATCHDOG',
168 22: 'S390_TSCH',
169 23: 'EPR',
Jens Freimannc5854ac2012-06-06 02:05:18 +0000170}
171
Michael Ellerman4d4103f2014-06-17 17:54:32 +1000172x86_exit_reasons = {
Jan Kiszka626c4272011-10-07 09:37:49 +0200173 'vmx': vmx_exit_reasons,
174 'svm': svm_exit_reasons,
175}
176
Michael Ellerman4d4103f2014-06-17 17:54:32 +1000177sc_perf_evt_open = None
Jan Kiszka626c4272011-10-07 09:37:49 +0200178exit_reasons = None
179
Michael Ellermana15d5642014-06-17 17:54:34 +1000180ioctl_numbers = {
181 'SET_FILTER' : 0x40082406,
182 'ENABLE' : 0x00002400,
183 'DISABLE' : 0x00002401,
184}
185
Michael Ellerman4d4103f2014-06-17 17:54:32 +1000186def x86_init(flag):
187 globals().update({
188 'sc_perf_evt_open' : 298,
189 'exit_reasons' : x86_exit_reasons[flag],
190 })
191
192def s390_init():
193 globals().update({
194 'sc_perf_evt_open' : 331
195 })
196
Michael Ellerman47253982014-06-17 17:54:35 +1000197def ppc_init():
198 globals().update({
199 'sc_perf_evt_open' : 319,
200 'ioctl_numbers' : {
201 'SET_FILTER' : 0x80002406 | (ctypes.sizeof(ctypes.c_char_p) << 16),
202 'ENABLE' : 0x20002400,
203 'DISABLE' : 0x20002401,
204 }
205 })
206
Wei Huangedcbc402015-01-21 16:15:29 -0500207def aarch64_init():
208 globals().update({
209 'sc_perf_evt_open' : 241
210 })
211
Michael Ellerman4d4103f2014-06-17 17:54:32 +1000212def detect_platform():
Michael Ellerman47253982014-06-17 17:54:35 +1000213 if os.uname()[4].startswith('ppc'):
214 ppc_init()
215 return
Wei Huangedcbc402015-01-21 16:15:29 -0500216 elif os.uname()[4].startswith('aarch64'):
217 aarch64_init()
218 return
Michael Ellerman47253982014-06-17 17:54:35 +1000219
Michael Ellerman4d4103f2014-06-17 17:54:32 +1000220 for line in file('/proc/cpuinfo').readlines():
221 if line.startswith('flags'):
222 for flag in line.split():
223 if flag in x86_exit_reasons:
224 x86_init(flag)
225 return
226 elif line.startswith('vendor_id'):
227 for flag in line.split():
228 if flag == 'IBM/S390':
229 s390_init()
230 return
231
232detect_platform()
Jan Kiszka626c4272011-10-07 09:37:49 +0200233
234def invert(d):
235 return dict((x[1], x[0]) for x in d.iteritems())
236
Michael Ellerman27d318a2014-06-17 17:54:31 +1000237filters = {}
238filters['kvm_userspace_exit'] = ('reason', invert(userspace_exit_reasons))
239if exit_reasons:
240 filters['kvm_exit'] = ('exit_reason', invert(exit_reasons))
Jan Kiszka626c4272011-10-07 09:37:49 +0200241
Michael Ellerman47253982014-06-17 17:54:35 +1000242import struct, array
Jan Kiszka626c4272011-10-07 09:37:49 +0200243
244libc = ctypes.CDLL('libc.so.6')
245syscall = libc.syscall
246class perf_event_attr(ctypes.Structure):
247 _fields_ = [('type', ctypes.c_uint32),
248 ('size', ctypes.c_uint32),
249 ('config', ctypes.c_uint64),
250 ('sample_freq', ctypes.c_uint64),
251 ('sample_type', ctypes.c_uint64),
252 ('read_format', ctypes.c_uint64),
253 ('flags', ctypes.c_uint64),
254 ('wakeup_events', ctypes.c_uint32),
255 ('bp_type', ctypes.c_uint32),
256 ('bp_addr', ctypes.c_uint64),
257 ('bp_len', ctypes.c_uint64),
258 ]
259def _perf_event_open(attr, pid, cpu, group_fd, flags):
Heinz Graalfs1b3e6f82012-10-29 02:13:20 +0000260 return syscall(sc_perf_evt_open, ctypes.pointer(attr), ctypes.c_int(pid),
Jan Kiszka626c4272011-10-07 09:37:49 +0200261 ctypes.c_int(cpu), ctypes.c_int(group_fd),
262 ctypes.c_long(flags))
263
264PERF_TYPE_HARDWARE = 0
265PERF_TYPE_SOFTWARE = 1
266PERF_TYPE_TRACEPOINT = 2
267PERF_TYPE_HW_CACHE = 3
268PERF_TYPE_RAW = 4
269PERF_TYPE_BREAKPOINT = 5
270
271PERF_SAMPLE_IP = 1 << 0
272PERF_SAMPLE_TID = 1 << 1
273PERF_SAMPLE_TIME = 1 << 2
274PERF_SAMPLE_ADDR = 1 << 3
275PERF_SAMPLE_READ = 1 << 4
276PERF_SAMPLE_CALLCHAIN = 1 << 5
277PERF_SAMPLE_ID = 1 << 6
278PERF_SAMPLE_CPU = 1 << 7
279PERF_SAMPLE_PERIOD = 1 << 8
280PERF_SAMPLE_STREAM_ID = 1 << 9
281PERF_SAMPLE_RAW = 1 << 10
282
283PERF_FORMAT_TOTAL_TIME_ENABLED = 1 << 0
284PERF_FORMAT_TOTAL_TIME_RUNNING = 1 << 1
285PERF_FORMAT_ID = 1 << 2
286PERF_FORMAT_GROUP = 1 << 3
287
288import re
289
290sys_tracing = '/sys/kernel/debug/tracing'
291
292class Group(object):
293 def __init__(self, cpu):
294 self.events = []
295 self.group_leader = None
296 self.cpu = cpu
297 def add_event(self, name, event_set, tracepoint, filter = None):
298 self.events.append(Event(group = self,
299 name = name, event_set = event_set,
300 tracepoint = tracepoint, filter = filter))
301 if len(self.events) == 1:
302 self.file = os.fdopen(self.events[0].fd)
303 def read(self):
304 bytes = 8 * (1 + len(self.events))
305 fmt = 'xxxxxxxx' + 'q' * len(self.events)
306 return dict(zip([event.name for event in self.events],
307 struct.unpack(fmt, self.file.read(bytes))))
308
309class Event(object):
310 def __init__(self, group, name, event_set, tracepoint, filter = None):
311 self.name = name
312 attr = perf_event_attr()
313 attr.type = PERF_TYPE_TRACEPOINT
314 attr.size = ctypes.sizeof(attr)
315 id_path = os.path.join(sys_tracing, 'events', event_set,
316 tracepoint, 'id')
317 id = int(file(id_path).read())
318 attr.config = id
319 attr.sample_type = (PERF_SAMPLE_RAW
320 | PERF_SAMPLE_TIME
321 | PERF_SAMPLE_CPU)
322 attr.sample_period = 1
323 attr.read_format = PERF_FORMAT_GROUP
324 group_leader = -1
325 if group.events:
326 group_leader = group.events[0].fd
327 fd = _perf_event_open(attr, -1, group.cpu, group_leader, 0)
328 if fd == -1:
329 raise Exception('perf_event_open failed')
330 if filter:
331 import fcntl
Michael Ellermana15d5642014-06-17 17:54:34 +1000332 fcntl.ioctl(fd, ioctl_numbers['SET_FILTER'], filter)
Jan Kiszka626c4272011-10-07 09:37:49 +0200333 self.fd = fd
334 def enable(self):
335 import fcntl
Michael Ellermana15d5642014-06-17 17:54:34 +1000336 fcntl.ioctl(self.fd, ioctl_numbers['ENABLE'], 0)
Jan Kiszka626c4272011-10-07 09:37:49 +0200337 def disable(self):
338 import fcntl
Michael Ellermana15d5642014-06-17 17:54:34 +1000339 fcntl.ioctl(self.fd, ioctl_numbers['DISABLE'], 0)
Jan Kiszka626c4272011-10-07 09:37:49 +0200340
341class TracepointProvider(object):
342 def __init__(self):
343 path = os.path.join(sys_tracing, 'events', 'kvm')
344 fields = [f
345 for f in os.listdir(path)
346 if os.path.isdir(os.path.join(path, f))]
347 extra = []
348 for f in fields:
349 if f in filters:
350 subfield, values = filters[f]
351 for name, number in values.iteritems():
352 extra.append(f + '(' + name + ')')
353 fields += extra
354 self._setup(fields)
355 self.select(fields)
356 def fields(self):
357 return self._fields
Michael Ellerman763952d2014-06-17 17:54:30 +1000358
359 def _online_cpus(self):
360 l = []
361 pattern = r'cpu([0-9]+)'
362 basedir = '/sys/devices/system/cpu'
363 for entry in os.listdir(basedir):
364 match = re.match(pattern, entry)
365 if not match:
366 continue
367 path = os.path.join(basedir, entry, 'online')
368 if os.path.exists(path) and open(path).read().strip() != '1':
369 continue
370 l.append(int(match.group(1)))
371 return l
372
Jan Kiszka626c4272011-10-07 09:37:49 +0200373 def _setup(self, _fields):
374 self._fields = _fields
Michael Ellerman763952d2014-06-17 17:54:30 +1000375 cpus = self._online_cpus()
Jan Kiszka626c4272011-10-07 09:37:49 +0200376 import resource
Michael Ellerman763952d2014-06-17 17:54:30 +1000377 nfiles = len(cpus) * 1000
Jan Kiszka626c4272011-10-07 09:37:49 +0200378 resource.setrlimit(resource.RLIMIT_NOFILE, (nfiles, nfiles))
379 events = []
380 self.group_leaders = []
Michael Ellerman763952d2014-06-17 17:54:30 +1000381 for cpu in cpus:
Jan Kiszka626c4272011-10-07 09:37:49 +0200382 group = Group(cpu)
383 for name in _fields:
384 tracepoint = name
385 filter = None
386 m = re.match(r'(.*)\((.*)\)', name)
387 if m:
388 tracepoint, sub = m.groups()
389 filter = '%s==%d\0' % (filters[tracepoint][0],
390 filters[tracepoint][1][sub])
391 event = group.add_event(name, event_set = 'kvm',
392 tracepoint = tracepoint,
393 filter = filter)
394 self.group_leaders.append(group)
395 def select(self, fields):
396 for group in self.group_leaders:
397 for event in group.events:
398 if event.name in fields:
399 event.enable()
400 else:
401 event.disable()
402 def read(self):
403 from collections import defaultdict
404 ret = defaultdict(int)
405 for group in self.group_leaders:
406 for name, val in group.read().iteritems():
407 ret[name] += val
408 return ret
409
410class Stats:
Paolo Bonzinib763adf2014-05-21 12:42:26 +0200411 def __init__(self, providers, fields = None):
412 self.providers = providers
Jan Kiszka626c4272011-10-07 09:37:49 +0200413 self.fields_filter = fields
414 self._update()
415 def _update(self):
416 def wanted(key):
417 import re
418 if not self.fields_filter:
419 return True
420 return re.match(self.fields_filter, key) is not None
Paolo Bonzinib763adf2014-05-21 12:42:26 +0200421 self.values = dict()
422 for d in providers:
423 provider_fields = [key for key in d.fields() if wanted(key)]
424 for key in provider_fields:
425 self.values[key] = None
426 d.select(provider_fields)
Jan Kiszka626c4272011-10-07 09:37:49 +0200427 def set_fields_filter(self, fields_filter):
428 self.fields_filter = fields_filter
429 self._update()
430 def get(self):
Paolo Bonzinib763adf2014-05-21 12:42:26 +0200431 for d in providers:
432 new = d.read()
433 for key in d.fields():
434 oldval = self.values.get(key, (0, 0))
435 newval = new[key]
436 newdelta = None
437 if oldval is not None:
438 newdelta = newval - oldval[0]
439 self.values[key] = (newval, newdelta)
Jan Kiszka626c4272011-10-07 09:37:49 +0200440 return self.values
441
442if not os.access('/sys/kernel/debug', os.F_OK):
443 print 'Please enable CONFIG_DEBUG_FS in your kernel'
444 sys.exit(1)
445if not os.access('/sys/kernel/debug/kvm', os.F_OK):
446 print "Please mount debugfs ('mount -t debugfs debugfs /sys/kernel/debug')"
447 print "and ensure the kvm modules are loaded"
448 sys.exit(1)
449
450label_width = 40
451number_width = 10
452
453def tui(screen, stats):
454 curses.use_default_colors()
455 curses.noecho()
456 drilldown = False
457 fields_filter = stats.fields_filter
458 def update_drilldown():
459 if not fields_filter:
460 if drilldown:
461 stats.set_fields_filter(None)
462 else:
463 stats.set_fields_filter(r'^[^\(]*$')
464 update_drilldown()
465 def refresh(sleeptime):
466 screen.erase()
467 screen.addstr(0, 0, 'kvm statistics')
468 row = 2
469 s = stats.get()
470 def sortkey(x):
471 if s[x][1]:
472 return (-s[x][1], -s[x][0])
473 else:
474 return (0, -s[x][0])
475 for key in sorted(s.keys(), key = sortkey):
476 if row >= screen.getmaxyx()[0]:
477 break
478 values = s[key]
479 if not values[0] and not values[1]:
480 break
481 col = 1
482 screen.addstr(row, col, key)
483 col += label_width
484 screen.addstr(row, col, '%10d' % (values[0],))
485 col += number_width
486 if values[1] is not None:
487 screen.addstr(row, col, '%8d' % (values[1] / sleeptime,))
488 row += 1
489 screen.refresh()
490
491 sleeptime = 0.25
492 while True:
493 refresh(sleeptime)
494 curses.halfdelay(int(sleeptime * 10))
495 sleeptime = 3
496 try:
497 c = screen.getkey()
498 if c == 'x':
499 drilldown = not drilldown
500 update_drilldown()
501 if c == 'q':
502 break
503 except KeyboardInterrupt:
504 break
505 except curses.error:
506 continue
507
508def batch(stats):
509 s = stats.get()
510 time.sleep(1)
511 s = stats.get()
512 for key in sorted(s.keys()):
513 values = s[key]
514 print '%-22s%10d%10d' % (key, values[0], values[1])
515
516def log(stats):
517 keys = sorted(stats.get().iterkeys())
518 def banner():
519 for k in keys:
520 print '%10s' % k[0:9],
521 print
522 def statline():
523 s = stats.get()
524 for k in keys:
525 print ' %9d' % s[k][1],
526 print
527 line = 0
528 banner_repeat = 20
529 while True:
530 time.sleep(1)
531 if line % banner_repeat == 0:
532 banner()
533 statline()
534 line += 1
535
536options = optparse.OptionParser()
537options.add_option('-1', '--once', '--batch',
538 action = 'store_true',
539 default = False,
540 dest = 'once',
541 help = 'run in batch mode for one second',
542 )
543options.add_option('-l', '--log',
544 action = 'store_true',
545 default = False,
546 dest = 'log',
547 help = 'run in logging mode (like vmstat)',
548 )
Paolo Bonzinib763adf2014-05-21 12:42:26 +0200549options.add_option('-t', '--tracepoints',
550 action = 'store_true',
551 default = False,
552 dest = 'tracepoints',
553 help = 'retrieve statistics from tracepoints',
554 )
555options.add_option('-d', '--debugfs',
556 action = 'store_true',
557 default = False,
558 dest = 'debugfs',
559 help = 'retrieve statistics from debugfs',
560 )
Jan Kiszka626c4272011-10-07 09:37:49 +0200561options.add_option('-f', '--fields',
562 action = 'store',
563 default = None,
564 dest = 'fields',
565 help = 'fields to display (regex)',
566 )
567(options, args) = options.parse_args(sys.argv)
568
Paolo Bonzinib763adf2014-05-21 12:42:26 +0200569providers = []
570if options.tracepoints:
571 providers.append(TracepointProvider())
572if options.debugfs:
573 providers.append(DebugfsProvider())
Jan Kiszka626c4272011-10-07 09:37:49 +0200574
Paolo Bonzinib763adf2014-05-21 12:42:26 +0200575if len(providers) == 0:
576 try:
577 providers = [TracepointProvider()]
578 except:
579 providers = [DebugfsProvider()]
580
581stats = Stats(providers, fields = options.fields)
Jan Kiszka626c4272011-10-07 09:37:49 +0200582
583if options.log:
584 log(stats)
585elif not options.once:
586 import curses.wrapper
587 curses.wrapper(tui, stats)
588else:
589 batch(stats)