root / scripts / kvm / kvm_stat @ 1b3e6f88
History | View | Annotate | Download (14.5 kB)
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 |
|
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 |
|
167 |
vendor_exit_reasons = { |
168 |
'vmx': vmx_exit_reasons, |
169 |
'svm': svm_exit_reasons, |
170 |
'IBM/S390': s390_exit_reasons, |
171 |
} |
172 |
|
173 |
syscall_numbers = { |
174 |
'IBM/S390': 331, |
175 |
} |
176 |
|
177 |
sc_perf_evt_open = 298 |
178 |
|
179 |
exit_reasons = None |
180 |
|
181 |
for line in file('/proc/cpuinfo').readlines(): |
182 |
if line.startswith('flags') or line.startswith('vendor_id'): |
183 |
for flag in line.split(): |
184 |
if flag in vendor_exit_reasons: |
185 |
exit_reasons = vendor_exit_reasons[flag] |
186 |
if flag in syscall_numbers: |
187 |
sc_perf_evt_open = syscall_numbers[flag] |
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): |
216 |
return syscall(sc_perf_evt_open, ctypes.pointer(attr), ctypes.c_int(pid), |
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) |