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