]> Git Repo - qemu.git/blob - scripts/kvm/kvm_stat
scripts/kvm/kvm_stat: Cleanup and pre-init perf_event_attr
[qemu.git] / scripts / kvm / kvm_stat
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 <[email protected]>
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
14 import curses
15 import sys
16 import os
17 import time
18 import optparse
19 import ctypes
20 import fcntl
21 import resource
22 import struct
23 import re
24 from collections import defaultdict
25
26 VMX_EXIT_REASONS = {
27     'EXCEPTION_NMI':        0,
28     'EXTERNAL_INTERRUPT':   1,
29     'TRIPLE_FAULT':         2,
30     'PENDING_INTERRUPT':    7,
31     'NMI_WINDOW':           8,
32     'TASK_SWITCH':          9,
33     'CPUID':                10,
34     'HLT':                  12,
35     'INVLPG':               14,
36     'RDPMC':                15,
37     'RDTSC':                16,
38     'VMCALL':               18,
39     'VMCLEAR':              19,
40     'VMLAUNCH':             20,
41     'VMPTRLD':              21,
42     'VMPTRST':              22,
43     'VMREAD':               23,
44     'VMRESUME':             24,
45     'VMWRITE':              25,
46     'VMOFF':                26,
47     'VMON':                 27,
48     'CR_ACCESS':            28,
49     'DR_ACCESS':            29,
50     'IO_INSTRUCTION':       30,
51     'MSR_READ':             31,
52     'MSR_WRITE':            32,
53     'INVALID_STATE':        33,
54     'MWAIT_INSTRUCTION':    36,
55     'MONITOR_INSTRUCTION':  39,
56     'PAUSE_INSTRUCTION':    40,
57     'MCE_DURING_VMENTRY':   41,
58     'TPR_BELOW_THRESHOLD':  43,
59     'APIC_ACCESS':          44,
60     'EPT_VIOLATION':        48,
61     'EPT_MISCONFIG':        49,
62     'WBINVD':               54,
63     'XSETBV':               55,
64     'APIC_WRITE':           56,
65     'INVPCID':              58,
66 }
67
68 SVM_EXIT_REASONS = {
69     'READ_CR0':       0x000,
70     'READ_CR3':       0x003,
71     'READ_CR4':       0x004,
72     'READ_CR8':       0x008,
73     'WRITE_CR0':      0x010,
74     'WRITE_CR3':      0x013,
75     'WRITE_CR4':      0x014,
76     'WRITE_CR8':      0x018,
77     'READ_DR0':       0x020,
78     'READ_DR1':       0x021,
79     'READ_DR2':       0x022,
80     'READ_DR3':       0x023,
81     'READ_DR4':       0x024,
82     'READ_DR5':       0x025,
83     'READ_DR6':       0x026,
84     'READ_DR7':       0x027,
85     'WRITE_DR0':      0x030,
86     'WRITE_DR1':      0x031,
87     'WRITE_DR2':      0x032,
88     'WRITE_DR3':      0x033,
89     'WRITE_DR4':      0x034,
90     'WRITE_DR5':      0x035,
91     'WRITE_DR6':      0x036,
92     'WRITE_DR7':      0x037,
93     'EXCP_BASE':      0x040,
94     'INTR':           0x060,
95     'NMI':            0x061,
96     'SMI':            0x062,
97     'INIT':           0x063,
98     'VINTR':          0x064,
99     'CR0_SEL_WRITE':  0x065,
100     'IDTR_READ':      0x066,
101     'GDTR_READ':      0x067,
102     'LDTR_READ':      0x068,
103     'TR_READ':        0x069,
104     'IDTR_WRITE':     0x06a,
105     'GDTR_WRITE':     0x06b,
106     'LDTR_WRITE':     0x06c,
107     'TR_WRITE':       0x06d,
108     'RDTSC':          0x06e,
109     'RDPMC':          0x06f,
110     'PUSHF':          0x070,
111     'POPF':           0x071,
112     'CPUID':          0x072,
113     'RSM':            0x073,
114     'IRET':           0x074,
115     'SWINT':          0x075,
116     'INVD':           0x076,
117     'PAUSE':          0x077,
118     'HLT':            0x078,
119     'INVLPG':         0x079,
120     'INVLPGA':        0x07a,
121     'IOIO':           0x07b,
122     'MSR':            0x07c,
123     'TASK_SWITCH':    0x07d,
124     'FERR_FREEZE':    0x07e,
125     'SHUTDOWN':       0x07f,
126     'VMRUN':          0x080,
127     'VMMCALL':        0x081,
128     'VMLOAD':         0x082,
129     'VMSAVE':         0x083,
130     'STGI':           0x084,
131     'CLGI':           0x085,
132     'SKINIT':         0x086,
133     'RDTSCP':         0x087,
134     'ICEBP':          0x088,
135     'WBINVD':         0x089,
136     'MONITOR':        0x08a,
137     'MWAIT':          0x08b,
138     'MWAIT_COND':     0x08c,
139     'XSETBV':         0x08d,
140     'NPF':            0x400,
141 }
142
143 # EC definition of HSR (from arch/arm64/include/asm/kvm_arm.h)
144 AARCH64_EXIT_REASONS = {
145     'UNKNOWN':      0x00,
146     'WFI':          0x01,
147     'CP15_32':      0x03,
148     'CP15_64':      0x04,
149     'CP14_MR':      0x05,
150     'CP14_LS':      0x06,
151     'FP_ASIMD':     0x07,
152     'CP10_ID':      0x08,
153     'CP14_64':      0x0C,
154     'ILL_ISS':      0x0E,
155     'SVC32':        0x11,
156     'HVC32':        0x12,
157     'SMC32':        0x13,
158     'SVC64':        0x15,
159     'HVC64':        0x16,
160     'SMC64':        0x17,
161     'SYS64':        0x18,
162     'IABT':         0x20,
163     'IABT_HYP':     0x21,
164     'PC_ALIGN':     0x22,
165     'DABT':         0x24,
166     'DABT_HYP':     0x25,
167     'SP_ALIGN':     0x26,
168     'FP_EXC32':     0x28,
169     'FP_EXC64':     0x2C,
170     'SERROR':       0x2F,
171     'BREAKPT':      0x30,
172     'BREAKPT_HYP':  0x31,
173     'SOFTSTP':      0x32,
174     'SOFTSTP_HYP':  0x33,
175     'WATCHPT':      0x34,
176     'WATCHPT_HYP':  0x35,
177     'BKPT32':       0x38,
178     'VECTOR32':     0x3A,
179     'BRK64':        0x3C,
180 }
181
182 # From include/uapi/linux/kvm.h, KVM_EXIT_xxx
183 USERSPACE_EXIT_REASONS = {
184     'UNKNOWN':          0,
185     'EXCEPTION':        1,
186     'IO':               2,
187     'HYPERCALL':        3,
188     'DEBUG':            4,
189     'HLT':              5,
190     'MMIO':             6,
191     'IRQ_WINDOW_OPEN':  7,
192     'SHUTDOWN':         8,
193     'FAIL_ENTRY':       9,
194     'INTR':             10,
195     'SET_TPR':          11,
196     'TPR_ACCESS':       12,
197     'S390_SIEIC':       13,
198     'S390_RESET':       14,
199     'DCR':              15,
200     'NMI':              16,
201     'INTERNAL_ERROR':   17,
202     'OSI':              18,
203     'PAPR_HCALL':       19,
204     'S390_UCONTROL':    20,
205     'WATCHDOG':         21,
206     'S390_TSCH':        22,
207     'EPR':              23,
208     'SYSTEM_EVENT':     24,
209 }
210
211 IOCTL_NUMBERS = {
212     'SET_FILTER':  0x40082406,
213     'ENABLE':      0x00002400,
214     'DISABLE':     0x00002401,
215     'RESET':       0x00002403,
216 }
217
218 class Arch(object):
219     """Class that encapsulates global architecture specific data like
220     syscall and ioctl numbers.
221
222     """
223     @staticmethod
224     def get_arch():
225         machine = os.uname()[4]
226
227         if machine.startswith('ppc'):
228             return ArchPPC()
229         elif machine.startswith('aarch64'):
230             return ArchA64()
231         elif machine.startswith('s390'):
232             return ArchS390()
233         else:
234             # X86_64
235             for line in open('/proc/cpuinfo'):
236                 if not line.startswith('flags'):
237                     continue
238
239                 flags = line.split()
240                 if 'vmx' in flags:
241                     return ArchX86(VMX_EXIT_REASONS)
242                 if 'svm' in flags:
243                     return ArchX86(SVM_EXIT_REASONS)
244                 return
245
246 class ArchX86(Arch):
247     def __init__(self, exit_reasons):
248         self.sc_perf_evt_open = 298
249         self.ioctl_numbers = IOCTL_NUMBERS
250         self.exit_reasons = exit_reasons
251
252 class ArchPPC(Arch):
253     def __init__(self):
254         self.sc_perf_evt_open = 319
255         self.ioctl_numbers = IOCTL_NUMBERS
256         self.ioctl_numbers['ENABLE'] = 0x20002400
257         self.ioctl_numbers['DISABLE'] = 0x20002401
258
259         # PPC comes in 32 and 64 bit and some generated ioctl
260         # numbers depend on the wordsize.
261         char_ptr_size = ctypes.sizeof(ctypes.c_char_p)
262         self.ioctl_numbers['SET_FILTER'] = 0x80002406 | char_ptr_size << 16
263
264 class ArchA64(Arch):
265     def __init__(self):
266         self.sc_perf_evt_open = 241
267         self.ioctl_numbers = IOCTL_NUMBERS
268         self.exit_reasons = AARCH64_EXIT_REASONS
269
270 class ArchS390(Arch):
271     def __init__(self):
272         self.sc_perf_evt_open = 331
273         self.ioctl_numbers = IOCTL_NUMBERS
274         self.exit_reasons = None
275
276 ARCH = Arch.get_arch()
277
278
279 def walkdir(path):
280     """Returns os.walk() data for specified directory.
281
282     As it is only a wrapper it returns the same 3-tuple of (dirpath,
283     dirnames, filenames).
284     """
285     return next(os.walk(path))
286
287
288 def parse_int_list(list_string):
289     """Returns an int list from a string of comma separated integers and
290     integer ranges."""
291     integers = []
292     members = list_string.split(',')
293
294     for member in members:
295         if '-' not in member:
296             integers.append(int(member))
297         else:
298             int_range = member.split('-')
299             integers.extend(range(int(int_range[0]),
300                                   int(int_range[1]) + 1))
301
302     return integers
303
304
305 def get_online_cpus():
306     with open('/sys/devices/system/cpu/online') as cpu_list:
307         cpu_string = cpu_list.readline()
308         return parse_int_list(cpu_string)
309
310
311 def get_filters():
312     filters = {}
313     filters['kvm_userspace_exit'] = ('reason', USERSPACE_EXIT_REASONS)
314     if ARCH.exit_reasons:
315         filters['kvm_exit'] = ('exit_reason', ARCH.exit_reasons)
316     return filters
317
318 libc = ctypes.CDLL('libc.so.6', use_errno=True)
319 syscall = libc.syscall
320
321 class perf_event_attr(ctypes.Structure):
322     _fields_ = [('type', ctypes.c_uint32),
323                 ('size', ctypes.c_uint32),
324                 ('config', ctypes.c_uint64),
325                 ('sample_freq', ctypes.c_uint64),
326                 ('sample_type', ctypes.c_uint64),
327                 ('read_format', ctypes.c_uint64),
328                 ('flags', ctypes.c_uint64),
329                 ('wakeup_events', ctypes.c_uint32),
330                 ('bp_type', ctypes.c_uint32),
331                 ('bp_addr', ctypes.c_uint64),
332                 ('bp_len', ctypes.c_uint64),
333                 ]
334
335     def __init__(self):
336         super(self.__class__, self).__init__()
337         self.type = PERF_TYPE_TRACEPOINT
338         self.size = ctypes.sizeof(self)
339         self.read_format = PERF_FORMAT_GROUP
340
341 def perf_event_open(attr, pid, cpu, group_fd, flags):
342     return syscall(ARCH.sc_perf_evt_open, ctypes.pointer(attr),
343                    ctypes.c_int(pid), ctypes.c_int(cpu),
344                    ctypes.c_int(group_fd), ctypes.c_long(flags))
345
346 PERF_TYPE_TRACEPOINT = 2
347 PERF_FORMAT_GROUP = 1 << 3
348
349 PATH_DEBUGFS_TRACING = '/sys/kernel/debug/tracing'
350 PATH_DEBUGFS_KVM = '/sys/kernel/debug/kvm'
351
352 class Group(object):
353     def __init__(self):
354         self.events = []
355
356     def add_event(self, event):
357         self.events.append(event)
358
359     def read(self):
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
366 class Event(object):
367     def __init__(self, name, group, trace_cpu, trace_point, trace_filter,
368                  trace_set='kvm'):
369         self.name = name
370         self.fd = None
371         self.setup_event(group, trace_cpu, trace_point, trace_filter,
372                          trace_set)
373
374     def setup_event_attribute(self, trace_set, trace_point):
375         id_path = os.path.join(PATH_DEBUGFS_TRACING, 'events', trace_set,
376                                trace_point, 'id')
377
378         event_attr = perf_event_attr()
379         event_attr.config = int(open(id_path).read())
380         return event_attr
381
382     def setup_event(self, group, trace_cpu, trace_point, trace_filter,
383                     trace_set):
384         event_attr = self.setup_event_attribute(trace_set, trace_point)
385
386         group_leader = -1
387         if group.events:
388             group_leader = group.events[0].fd
389
390         fd = perf_event_open(event_attr, -1, trace_cpu,
391                              group_leader, 0)
392         if fd == -1:
393             err = ctypes.get_errno()
394             raise OSError(err, os.strerror(err),
395                           'while calling sys_perf_event_open().')
396
397         if trace_filter:
398             fcntl.ioctl(fd, ARCH.ioctl_numbers['SET_FILTER'],
399                         trace_filter)
400
401         self.fd = fd
402
403     def enable(self):
404         fcntl.ioctl(self.fd, ARCH.ioctl_numbers['ENABLE'], 0)
405
406     def disable(self):
407         fcntl.ioctl(self.fd, ARCH.ioctl_numbers['DISABLE'], 0)
408
409     def reset(self):
410         fcntl.ioctl(self.fd, ARCH.ioctl_numbers['RESET'], 0)
411
412 class TracepointProvider(object):
413     def __init__(self):
414         self.group_leaders = []
415         self.filters = get_filters()
416         self._fields = self.get_available_fields()
417         self.setup_traces()
418         self.fields = self._fields
419
420     def get_available_fields(self):
421         path = os.path.join(PATH_DEBUGFS_TRACING, 'events', 'kvm')
422         fields = walkdir(path)[1]
423         extra = []
424         for field in fields:
425             if field in self.filters:
426                 filter_name_, filter_dicts = self.filters[field]
427                 for name in filter_dicts:
428                     extra.append(field + '(' + name + ')')
429         fields += extra
430         return fields
431
432     def setup_traces(self):
433         cpus = get_online_cpus()
434
435         # The constant is needed as a buffer for python libs, std
436         # streams and other files that the script opens.
437         rlimit = len(cpus) * len(self._fields) + 50
438         try:
439             resource.setrlimit(resource.RLIMIT_NOFILE, (rlimit, rlimit))
440         except ValueError:
441             sys.exit("NOFILE rlimit could not be raised to {0}".format(rlimit))
442
443         for cpu in cpus:
444             group = Group()
445             for name in self._fields:
446                 tracepoint = name
447                 tracefilter = None
448                 match = re.match(r'(.*)\((.*)\)', name)
449                 if match:
450                     tracepoint, sub = match.groups()
451                     tracefilter = ('%s==%d\0' %
452                                    (self.filters[tracepoint][0],
453                                     self.filters[tracepoint][1][sub]))
454
455                 group.add_event(Event(name=name,
456                                       group=group,
457                                       trace_cpu=cpu,
458                                       trace_point=tracepoint,
459                                       trace_filter=tracefilter))
460             self.group_leaders.append(group)
461
462     @property
463     def fields(self):
464         return self._fields
465
466     @fields.setter
467     def fields(self, fields):
468         self._fields = fields
469         for group in self.group_leaders:
470             for event in group.events:
471                 if event.name in fields:
472                     event.reset()
473                     event.enable()
474                 else:
475                     event.disable()
476
477     def read(self):
478         ret = defaultdict(int)
479         for group in self.group_leaders:
480             for name, val in group.read().iteritems():
481                 ret[name] += val
482         return ret
483
484 class DebugfsProvider(object):
485     def __init__(self):
486         self._fields = walkdir(PATH_DEBUGFS_KVM)[2]
487
488     @property
489     def fields(self):
490         return self._fields
491
492     @fields.setter
493     def fields(self, fields):
494         self._fields = fields
495
496     def read(self):
497         def val(key):
498             return int(file(PATH_DEBUGFS_KVM + '/' + key).read())
499         return dict([(key, val(key)) for key in self._fields])
500
501 class Stats(object):
502     def __init__(self, providers, fields=None):
503         self.providers = providers
504         self._fields_filter = fields
505         self.values = {}
506         self.update_provider_filters()
507
508     def update_provider_filters(self):
509         def wanted(key):
510             if not self._fields_filter:
511                 return True
512             return re.match(self._fields_filter, key) is not None
513
514         # As we reset the counters when updating the fields we can
515         # also clear the cache of old values.
516         self.values = {}
517         for provider in self.providers:
518             provider_fields = [key for key in provider.fields if wanted(key)]
519             provider.fields = provider_fields
520
521     @property
522     def fields_filter(self):
523         return self._fields_filter
524
525     @fields_filter.setter
526     def fields_filter(self, fields_filter):
527         self._fields_filter = fields_filter
528         self.update_provider_filters()
529
530     def get(self):
531         for provider in self.providers:
532             new = provider.read()
533             for key in provider.fields:
534                 oldval = self.values.get(key, (0, 0))
535                 newval = new.get(key, 0)
536                 newdelta = None
537                 if oldval is not None:
538                     newdelta = newval - oldval[0]
539                 self.values[key] = (newval, newdelta)
540         return self.values
541
542 LABEL_WIDTH = 40
543 NUMBER_WIDTH = 10
544
545 class Tui(object):
546     def __init__(self, stats):
547         self.stats = stats
548         self.screen = None
549         self.drilldown = False
550         self.fields_filter = self.stats.fields_filter
551         self.update_drilldown()
552
553     def __enter__(self):
554         """Initialises curses for later use.  Based on curses.wrapper
555            implementation from the Python standard library."""
556         self.screen = curses.initscr()
557         curses.noecho()
558         curses.cbreak()
559
560         # The try/catch works around a minor bit of
561         # over-conscientiousness in the curses module, the error
562         # return from C start_color() is ignorable.
563         try:
564             curses.start_color()
565         except:
566             pass
567
568         curses.use_default_colors()
569         return self
570
571     def __exit__(self, *exception):
572         """Resets the terminal to its normal state.  Based on curses.wrappre
573            implementation from the Python standard library."""
574         if self.screen:
575             self.screen.keypad(0)
576             curses.echo()
577             curses.nocbreak()
578             curses.endwin()
579
580     def update_drilldown(self):
581         if not self.fields_filter:
582             if self.drilldown:
583                 self.stats.fields_filter = None
584             else:
585                 self.stats.fields_filter = r'^[^\(]*$'
586
587     def refresh(self, sleeptime):
588         self.screen.erase()
589         self.screen.addstr(0, 0, 'kvm statistics - summary', curses.A_BOLD)
590         self.screen.addstr(2, 1, 'Event')
591         self.screen.addstr(2, 1 + LABEL_WIDTH + NUMBER_WIDTH -
592                            len('Total'), 'Total')
593         self.screen.addstr(2, 1 + LABEL_WIDTH + NUMBER_WIDTH + 8 -
594                            len('Current'), 'Current')
595         row = 3
596         stats = self.stats.get()
597         def sortkey(x):
598             if stats[x][1]:
599                 return (-stats[x][1], -stats[x][0])
600             else:
601                 return (0, -stats[x][0])
602         for key in sorted(stats.keys(), key=sortkey):
603
604             if row >= self.screen.getmaxyx()[0]:
605                 break
606             values = stats[key]
607             if not values[0] and not values[1]:
608                 break
609             col = 1
610             self.screen.addstr(row, col, key)
611             col += LABEL_WIDTH
612             self.screen.addstr(row, col, '%10d' % (values[0],))
613             col += NUMBER_WIDTH
614             if values[1] is not None:
615                 self.screen.addstr(row, col, '%8d' % (values[1] / sleeptime,))
616             row += 1
617         self.screen.refresh()
618
619     def show_stats(self):
620         sleeptime = 0.25
621         while True:
622             self.refresh(sleeptime)
623             curses.halfdelay(int(sleeptime * 10))
624             sleeptime = 3
625             try:
626                 char = self.screen.getkey()
627                 if char == 'x':
628                     self.drilldown = not self.drilldown
629                     self.update_drilldown()
630                 if char == 'q':
631                     break
632             except KeyboardInterrupt:
633                 break
634             except curses.error:
635                 continue
636
637 def batch(stats):
638     s = stats.get()
639     time.sleep(1)
640     s = stats.get()
641     for key in sorted(s.keys()):
642         values = s[key]
643         print '%-42s%10d%10d' % (key, values[0], values[1])
644
645 def log(stats):
646     keys = sorted(stats.get().iterkeys())
647     def banner():
648         for k in keys:
649             print '%s' % k,
650         print
651     def statline():
652         s = stats.get()
653         for k in keys:
654             print ' %9d' % s[k][1],
655         print
656     line = 0
657     banner_repeat = 20
658     while True:
659         time.sleep(1)
660         if line % banner_repeat == 0:
661             banner()
662         statline()
663         line += 1
664
665 def get_options():
666     optparser = optparse.OptionParser()
667     optparser.add_option('-1', '--once', '--batch',
668                          action='store_true',
669                          default=False,
670                          dest='once',
671                          help='run in batch mode for one second',
672                          )
673     optparser.add_option('-l', '--log',
674                          action='store_true',
675                          default=False,
676                          dest='log',
677                          help='run in logging mode (like vmstat)',
678                          )
679     optparser.add_option('-t', '--tracepoints',
680                          action='store_true',
681                          default=False,
682                          dest='tracepoints',
683                          help='retrieve statistics from tracepoints',
684                          )
685     optparser.add_option('-d', '--debugfs',
686                          action='store_true',
687                          default=False,
688                          dest='debugfs',
689                          help='retrieve statistics from debugfs',
690                          )
691     optparser.add_option('-f', '--fields',
692                          action='store',
693                          default=None,
694                          dest='fields',
695                          help='fields to display (regex)',
696                          )
697     (options, _) = optparser.parse_args(sys.argv)
698     return options
699
700 def get_providers(options):
701     providers = []
702
703     if options.tracepoints:
704         providers.append(TracepointProvider())
705     if options.debugfs:
706         providers.append(DebugfsProvider())
707     if len(providers) == 0:
708         providers.append(TracepointProvider())
709
710     return providers
711
712 def check_access():
713     if not os.path.exists('/sys/kernel/debug'):
714         sys.stderr.write('Please enable CONFIG_DEBUG_FS in your kernel.')
715         sys.exit(1)
716
717     if not os.path.exists(PATH_DEBUGFS_KVM):
718         sys.stderr.write("Please make sure, that debugfs is mounted and "
719                          "readable by the current user:\n"
720                          "('mount -t debugfs debugfs /sys/kernel/debug')\n"
721                          "Also ensure, that the kvm modules are loaded.\n")
722         sys.exit(1)
723
724     if not os.path.exists(PATH_DEBUGFS_TRACING):
725         sys.stderr.write("Please make {0} readable by the current user.\n"
726                          .format(PATH_DEBUGFS_TRACING))
727         sys.exit(1)
728
729 def main():
730     check_access()
731     options = get_options()
732     providers = get_providers(options)
733     stats = Stats(providers, fields=options.fields)
734
735     if options.log:
736         log(stats)
737     elif not options.once:
738         with Tui(stats) as tui:
739             tui.show_stats()
740     else:
741         batch(stats)
742
743 if __name__ == "__main__":
744     main()
This page took 0.073273 seconds and 4 git commands to generate.