|
|
1.1 root 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: print
432: def statline():
433: s = stats.get()
434: for k in keys:
435: print ' %9d' % s[k][1],
436: print
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)
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.