]>
Commit | Line | Data |
---|---|---|
626c4276 JK |
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. | |
13 | ||
14 | import curses | |
15 | import sys, os, time, optparse | |
16 | ||
17 | class 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 | ||
30 | vmx_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 | ||
70 | svm_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 | ||
c5854acb JF |
144 | s390_exit_reasons = { |
145 | 0x000: 'UNKNOWN', | |
146 | 0x001: 'EXCEPTION', | |
147 | 0x002: 'IO', | |
148 | 0x003: 'HYPERCALL', | |
149 | 0x004: 'DEBUG', | |
150 | 0x005: 'HLT', | |
151 | 0x006: 'MMIO', | |
152 | 0x007: 'IRQ_WINDOW_OPEN', | |
153 | 0x008: 'SHUTDOWN', | |
154 | 0x009: 'FAIL_ENTRY', | |
155 | 0x010: 'INTR', | |
156 | 0x011: 'SET_TPR', | |
157 | 0x012: 'TPR_ACCESS', | |
158 | 0x013: 'S390_SIEIC', | |
159 | 0x014: 'S390_RESET', | |
160 | 0x015: 'DCR', | |
161 | 0x016: 'NMI', | |
162 | 0x017: 'INTERNAL_ERROR', | |
163 | 0x018: 'OSI', | |
164 | 0x019: 'PAPR_HCALL', | |
165 | } | |
166 | ||
626c4276 JK |
167 | vendor_exit_reasons = { |
168 | 'vmx': vmx_exit_reasons, | |
169 | 'svm': svm_exit_reasons, | |
c5854acb | 170 | 'IBM/S390': s390_exit_reasons, |
626c4276 JK |
171 | } |
172 | ||
1b3e6f88 HG |
173 | syscall_numbers = { |
174 | 'IBM/S390': 331, | |
175 | } | |
176 | ||
177 | sc_perf_evt_open = 298 | |
178 | ||
626c4276 JK |
179 | exit_reasons = None |
180 | ||
181 | for line in file('/proc/cpuinfo').readlines(): | |
c5854acb | 182 | if line.startswith('flags') or line.startswith('vendor_id'): |
626c4276 JK |
183 | for flag in line.split(): |
184 | if flag in vendor_exit_reasons: | |
185 | exit_reasons = vendor_exit_reasons[flag] | |
1b3e6f88 HG |
186 | if flag in syscall_numbers: |
187 | sc_perf_evt_open = syscall_numbers[flag] | |
626c4276 JK |
188 | filters = { |
189 | 'kvm_exit': ('exit_reason', exit_reasons) | |
190 | } | |
191 | ||
192 | def invert(d): | |
193 | return dict((x[1], x[0]) for x in d.iteritems()) | |
194 | ||
195 | for f in filters: | |
196 | filters[f] = (filters[f][0], invert(filters[f][1])) | |
197 | ||
198 | import ctypes, struct, array | |
199 | ||
200 | libc = ctypes.CDLL('libc.so.6') | |
201 | syscall = libc.syscall | |
202 | class perf_event_attr(ctypes.Structure): | |
203 | _fields_ = [('type', ctypes.c_uint32), | |
204 | ('size', ctypes.c_uint32), | |
205 | ('config', ctypes.c_uint64), | |
206 | ('sample_freq', ctypes.c_uint64), | |
207 | ('sample_type', ctypes.c_uint64), | |
208 | ('read_format', ctypes.c_uint64), | |
209 | ('flags', ctypes.c_uint64), | |
210 | ('wakeup_events', ctypes.c_uint32), | |
211 | ('bp_type', ctypes.c_uint32), | |
212 | ('bp_addr', ctypes.c_uint64), | |
213 | ('bp_len', ctypes.c_uint64), | |
214 | ] | |
215 | def _perf_event_open(attr, pid, cpu, group_fd, flags): | |
1b3e6f88 | 216 | return syscall(sc_perf_evt_open, ctypes.pointer(attr), ctypes.c_int(pid), |
626c4276 JK |
217 | ctypes.c_int(cpu), ctypes.c_int(group_fd), |
218 | ctypes.c_long(flags)) | |
219 | ||
220 | PERF_TYPE_HARDWARE = 0 | |
221 | PERF_TYPE_SOFTWARE = 1 | |
222 | PERF_TYPE_TRACEPOINT = 2 | |
223 | PERF_TYPE_HW_CACHE = 3 | |
224 | PERF_TYPE_RAW = 4 | |
225 | PERF_TYPE_BREAKPOINT = 5 | |
226 | ||
227 | PERF_SAMPLE_IP = 1 << 0 | |
228 | PERF_SAMPLE_TID = 1 << 1 | |
229 | PERF_SAMPLE_TIME = 1 << 2 | |
230 | PERF_SAMPLE_ADDR = 1 << 3 | |
231 | PERF_SAMPLE_READ = 1 << 4 | |
232 | PERF_SAMPLE_CALLCHAIN = 1 << 5 | |
233 | PERF_SAMPLE_ID = 1 << 6 | |
234 | PERF_SAMPLE_CPU = 1 << 7 | |
235 | PERF_SAMPLE_PERIOD = 1 << 8 | |
236 | PERF_SAMPLE_STREAM_ID = 1 << 9 | |
237 | PERF_SAMPLE_RAW = 1 << 10 | |
238 | ||
239 | PERF_FORMAT_TOTAL_TIME_ENABLED = 1 << 0 | |
240 | PERF_FORMAT_TOTAL_TIME_RUNNING = 1 << 1 | |
241 | PERF_FORMAT_ID = 1 << 2 | |
242 | PERF_FORMAT_GROUP = 1 << 3 | |
243 | ||
244 | import re | |
245 | ||
246 | sys_tracing = '/sys/kernel/debug/tracing' | |
247 | ||
248 | class Group(object): | |
249 | def __init__(self, cpu): | |
250 | self.events = [] | |
251 | self.group_leader = None | |
252 | self.cpu = cpu | |
253 | def add_event(self, name, event_set, tracepoint, filter = None): | |
254 | self.events.append(Event(group = self, | |
255 | name = name, event_set = event_set, | |
256 | tracepoint = tracepoint, filter = filter)) | |
257 | if len(self.events) == 1: | |
258 | self.file = os.fdopen(self.events[0].fd) | |
259 | def read(self): | |
260 | bytes = 8 * (1 + len(self.events)) | |
261 | fmt = 'xxxxxxxx' + 'q' * len(self.events) | |
262 | return dict(zip([event.name for event in self.events], | |
263 | struct.unpack(fmt, self.file.read(bytes)))) | |
264 | ||
265 | class Event(object): | |
266 | def __init__(self, group, name, event_set, tracepoint, filter = None): | |
267 | self.name = name | |
268 | attr = perf_event_attr() | |
269 | attr.type = PERF_TYPE_TRACEPOINT | |
270 | attr.size = ctypes.sizeof(attr) | |
271 | id_path = os.path.join(sys_tracing, 'events', event_set, | |
272 | tracepoint, 'id') | |
273 | id = int(file(id_path).read()) | |
274 | attr.config = id | |
275 | attr.sample_type = (PERF_SAMPLE_RAW | |
276 | | PERF_SAMPLE_TIME | |
277 | | PERF_SAMPLE_CPU) | |
278 | attr.sample_period = 1 | |
279 | attr.read_format = PERF_FORMAT_GROUP | |
280 | group_leader = -1 | |
281 | if group.events: | |
282 | group_leader = group.events[0].fd | |
283 | fd = _perf_event_open(attr, -1, group.cpu, group_leader, 0) | |
284 | if fd == -1: | |
285 | raise Exception('perf_event_open failed') | |
286 | if filter: | |
287 | import fcntl | |
288 | fcntl.ioctl(fd, 0x40082406, filter) | |
289 | self.fd = fd | |
290 | def enable(self): | |
291 | import fcntl | |
292 | fcntl.ioctl(self.fd, 0x00002400, 0) | |
293 | def disable(self): | |
294 | import fcntl | |
295 | fcntl.ioctl(self.fd, 0x00002401, 0) | |
296 | ||
297 | class TracepointProvider(object): | |
298 | def __init__(self): | |
299 | path = os.path.join(sys_tracing, 'events', 'kvm') | |
300 | fields = [f | |
301 | for f in os.listdir(path) | |
302 | if os.path.isdir(os.path.join(path, f))] | |
303 | extra = [] | |
304 | for f in fields: | |
305 | if f in filters: | |
306 | subfield, values = filters[f] | |
307 | for name, number in values.iteritems(): | |
308 | extra.append(f + '(' + name + ')') | |
309 | fields += extra | |
310 | self._setup(fields) | |
311 | self.select(fields) | |
312 | def fields(self): | |
313 | return self._fields | |
314 | def _setup(self, _fields): | |
315 | self._fields = _fields | |
316 | cpure = r'cpu([0-9]+)' | |
317 | self.cpus = [int(re.match(cpure, x).group(1)) | |
318 | for x in os.listdir('/sys/devices/system/cpu') | |
319 | if re.match(cpure, x)] | |
320 | import resource | |
321 | nfiles = len(self.cpus) * 1000 | |
322 | resource.setrlimit(resource.RLIMIT_NOFILE, (nfiles, nfiles)) | |
323 | events = [] | |
324 | self.group_leaders = [] | |
325 | for cpu in self.cpus: | |
326 | group = Group(cpu) | |
327 | for name in _fields: | |
328 | tracepoint = name | |
329 | filter = None | |
330 | m = re.match(r'(.*)\((.*)\)', name) | |
331 | if m: | |
332 | tracepoint, sub = m.groups() | |
333 | filter = '%s==%d\0' % (filters[tracepoint][0], | |
334 | filters[tracepoint][1][sub]) | |
335 | event = group.add_event(name, event_set = 'kvm', | |
336 | tracepoint = tracepoint, | |
337 | filter = filter) | |
338 | self.group_leaders.append(group) | |
339 | def select(self, fields): | |
340 | for group in self.group_leaders: | |
341 | for event in group.events: | |
342 | if event.name in fields: | |
343 | event.enable() | |
344 | else: | |
345 | event.disable() | |
346 | def read(self): | |
347 | from collections import defaultdict | |
348 | ret = defaultdict(int) | |
349 | for group in self.group_leaders: | |
350 | for name, val in group.read().iteritems(): | |
351 | ret[name] += val | |
352 | return ret | |
353 | ||
354 | class Stats: | |
355 | def __init__(self, provider, fields = None): | |
356 | self.provider = provider | |
357 | self.fields_filter = fields | |
358 | self._update() | |
359 | def _update(self): | |
360 | def wanted(key): | |
361 | import re | |
362 | if not self.fields_filter: | |
363 | return True | |
364 | return re.match(self.fields_filter, key) is not None | |
365 | self.values = dict([(key, None) | |
366 | for key in provider.fields() | |
367 | if wanted(key)]) | |
368 | self.provider.select(self.values.keys()) | |
369 | def set_fields_filter(self, fields_filter): | |
370 | self.fields_filter = fields_filter | |
371 | self._update() | |
372 | def get(self): | |
373 | new = self.provider.read() | |
374 | for key in self.provider.fields(): | |
375 | oldval = self.values.get(key, (0, 0)) | |
376 | newval = new[key] | |
377 | newdelta = None | |
378 | if oldval is not None: | |
379 | newdelta = newval - oldval[0] | |
380 | self.values[key] = (newval, newdelta) | |
381 | return self.values | |
382 | ||
383 | if not os.access('/sys/kernel/debug', os.F_OK): | |
384 | print 'Please enable CONFIG_DEBUG_FS in your kernel' | |
385 | sys.exit(1) | |
386 | if not os.access('/sys/kernel/debug/kvm', os.F_OK): | |
387 | print "Please mount debugfs ('mount -t debugfs debugfs /sys/kernel/debug')" | |
388 | print "and ensure the kvm modules are loaded" | |
389 | sys.exit(1) | |
390 | ||
391 | label_width = 40 | |
392 | number_width = 10 | |
393 | ||
394 | def tui(screen, stats): | |
395 | curses.use_default_colors() | |
396 | curses.noecho() | |
397 | drilldown = False | |
398 | fields_filter = stats.fields_filter | |
399 | def update_drilldown(): | |
400 | if not fields_filter: | |
401 | if drilldown: | |
402 | stats.set_fields_filter(None) | |
403 | else: | |
404 | stats.set_fields_filter(r'^[^\(]*$') | |
405 | update_drilldown() | |
406 | def refresh(sleeptime): | |
407 | screen.erase() | |
408 | screen.addstr(0, 0, 'kvm statistics') | |
409 | row = 2 | |
410 | s = stats.get() | |
411 | def sortkey(x): | |
412 | if s[x][1]: | |
413 | return (-s[x][1], -s[x][0]) | |
414 | else: | |
415 | return (0, -s[x][0]) | |
416 | for key in sorted(s.keys(), key = sortkey): | |
417 | if row >= screen.getmaxyx()[0]: | |
418 | break | |
419 | values = s[key] | |
420 | if not values[0] and not values[1]: | |
421 | break | |
422 | col = 1 | |
423 | screen.addstr(row, col, key) | |
424 | col += label_width | |
425 | screen.addstr(row, col, '%10d' % (values[0],)) | |
426 | col += number_width | |
427 | if values[1] is not None: | |
428 | screen.addstr(row, col, '%8d' % (values[1] / sleeptime,)) | |
429 | row += 1 | |
430 | screen.refresh() | |
431 | ||
432 | sleeptime = 0.25 | |
433 | while True: | |
434 | refresh(sleeptime) | |
435 | curses.halfdelay(int(sleeptime * 10)) | |
436 | sleeptime = 3 | |
437 | try: | |
438 | c = screen.getkey() | |
439 | if c == 'x': | |
440 | drilldown = not drilldown | |
441 | update_drilldown() | |
442 | if c == 'q': | |
443 | break | |
444 | except KeyboardInterrupt: | |
445 | break | |
446 | except curses.error: | |
447 | continue | |
448 | ||
449 | def batch(stats): | |
450 | s = stats.get() | |
451 | time.sleep(1) | |
452 | s = stats.get() | |
453 | for key in sorted(s.keys()): | |
454 | values = s[key] | |
455 | print '%-22s%10d%10d' % (key, values[0], values[1]) | |
456 | ||
457 | def log(stats): | |
458 | keys = sorted(stats.get().iterkeys()) | |
459 | def banner(): | |
460 | for k in keys: | |
461 | print '%10s' % k[0:9], | |
462 | ||
463 | def statline(): | |
464 | s = stats.get() | |
465 | for k in keys: | |
466 | print ' %9d' % s[k][1], | |
467 | ||
468 | line = 0 | |
469 | banner_repeat = 20 | |
470 | while True: | |
471 | time.sleep(1) | |
472 | if line % banner_repeat == 0: | |
473 | banner() | |
474 | statline() | |
475 | line += 1 | |
476 | ||
477 | options = optparse.OptionParser() | |
478 | options.add_option('-1', '--once', '--batch', | |
479 | action = 'store_true', | |
480 | default = False, | |
481 | dest = 'once', | |
482 | help = 'run in batch mode for one second', | |
483 | ) | |
484 | options.add_option('-l', '--log', | |
485 | action = 'store_true', | |
486 | default = False, | |
487 | dest = 'log', | |
488 | help = 'run in logging mode (like vmstat)', | |
489 | ) | |
490 | options.add_option('-f', '--fields', | |
491 | action = 'store', | |
492 | default = None, | |
493 | dest = 'fields', | |
494 | help = 'fields to display (regex)', | |
495 | ) | |
496 | (options, args) = options.parse_args(sys.argv) | |
497 | ||
498 | try: | |
499 | provider = TracepointProvider() | |
500 | except: | |
501 | provider = DebugfsProvider() | |
502 | ||
503 | stats = Stats(provider, fields = options.fields) | |
504 | ||
505 | if options.log: | |
506 | log(stats) | |
507 | elif not options.once: | |
508 | import curses.wrapper | |
509 | curses.wrapper(tui, stats) | |
510 | else: | |
511 | batch(stats) |