Jan Kiszka | 626c427 | 2011-10-07 09:37:49 +0200 | [diff] [blame] | 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 |
Michael Ellerman | 4725398 | 2014-06-17 17:54:35 +1000 | [diff] [blame] | 15 | import sys, os, time, optparse, ctypes |
Jan Kiszka | 626c427 | 2011-10-07 09:37:49 +0200 | [diff] [blame] | 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 | |
Michael Ellerman | 27d318a | 2014-06-17 17:54:31 +1000 | [diff] [blame] | 144 | # From include/uapi/linux/kvm.h, KVM_EXIT_xxx |
| 145 | userspace_exit_reasons = { |
| 146 | 0: 'UNKNOWN', |
| 147 | 1: 'EXCEPTION', |
| 148 | 2: 'IO', |
| 149 | 3: 'HYPERCALL', |
| 150 | 4: 'DEBUG', |
| 151 | 5: 'HLT', |
| 152 | 6: 'MMIO', |
| 153 | 7: 'IRQ_WINDOW_OPEN', |
| 154 | 8: 'SHUTDOWN', |
| 155 | 9: 'FAIL_ENTRY', |
| 156 | 10: 'INTR', |
| 157 | 11: 'SET_TPR', |
| 158 | 12: 'TPR_ACCESS', |
| 159 | 13: 'S390_SIEIC', |
| 160 | 14: 'S390_RESET', |
| 161 | 15: 'DCR', |
| 162 | 16: 'NMI', |
| 163 | 17: 'INTERNAL_ERROR', |
| 164 | 18: 'OSI', |
| 165 | 19: 'PAPR_HCALL', |
| 166 | 20: 'S390_UCONTROL', |
| 167 | 21: 'WATCHDOG', |
| 168 | 22: 'S390_TSCH', |
| 169 | 23: 'EPR', |
Jens Freimann | c5854ac | 2012-06-06 02:05:18 +0000 | [diff] [blame] | 170 | } |
| 171 | |
Michael Ellerman | 4d4103f | 2014-06-17 17:54:32 +1000 | [diff] [blame] | 172 | x86_exit_reasons = { |
Jan Kiszka | 626c427 | 2011-10-07 09:37:49 +0200 | [diff] [blame] | 173 | 'vmx': vmx_exit_reasons, |
| 174 | 'svm': svm_exit_reasons, |
| 175 | } |
| 176 | |
Michael Ellerman | 4d4103f | 2014-06-17 17:54:32 +1000 | [diff] [blame] | 177 | sc_perf_evt_open = None |
Jan Kiszka | 626c427 | 2011-10-07 09:37:49 +0200 | [diff] [blame] | 178 | exit_reasons = None |
| 179 | |
Michael Ellerman | a15d564 | 2014-06-17 17:54:34 +1000 | [diff] [blame] | 180 | ioctl_numbers = { |
| 181 | 'SET_FILTER' : 0x40082406, |
| 182 | 'ENABLE' : 0x00002400, |
| 183 | 'DISABLE' : 0x00002401, |
| 184 | } |
| 185 | |
Michael Ellerman | 4d4103f | 2014-06-17 17:54:32 +1000 | [diff] [blame] | 186 | def x86_init(flag): |
| 187 | globals().update({ |
| 188 | 'sc_perf_evt_open' : 298, |
| 189 | 'exit_reasons' : x86_exit_reasons[flag], |
| 190 | }) |
| 191 | |
| 192 | def s390_init(): |
| 193 | globals().update({ |
| 194 | 'sc_perf_evt_open' : 331 |
| 195 | }) |
| 196 | |
Michael Ellerman | 4725398 | 2014-06-17 17:54:35 +1000 | [diff] [blame] | 197 | def ppc_init(): |
| 198 | globals().update({ |
| 199 | 'sc_perf_evt_open' : 319, |
| 200 | 'ioctl_numbers' : { |
| 201 | 'SET_FILTER' : 0x80002406 | (ctypes.sizeof(ctypes.c_char_p) << 16), |
| 202 | 'ENABLE' : 0x20002400, |
| 203 | 'DISABLE' : 0x20002401, |
| 204 | } |
| 205 | }) |
| 206 | |
Michael Ellerman | 4d4103f | 2014-06-17 17:54:32 +1000 | [diff] [blame] | 207 | def detect_platform(): |
Michael Ellerman | 4725398 | 2014-06-17 17:54:35 +1000 | [diff] [blame] | 208 | if os.uname()[4].startswith('ppc'): |
| 209 | ppc_init() |
| 210 | return |
| 211 | |
Michael Ellerman | 4d4103f | 2014-06-17 17:54:32 +1000 | [diff] [blame] | 212 | for line in file('/proc/cpuinfo').readlines(): |
| 213 | if line.startswith('flags'): |
| 214 | for flag in line.split(): |
| 215 | if flag in x86_exit_reasons: |
| 216 | x86_init(flag) |
| 217 | return |
| 218 | elif line.startswith('vendor_id'): |
| 219 | for flag in line.split(): |
| 220 | if flag == 'IBM/S390': |
| 221 | s390_init() |
| 222 | return |
| 223 | |
| 224 | detect_platform() |
Jan Kiszka | 626c427 | 2011-10-07 09:37:49 +0200 | [diff] [blame] | 225 | |
| 226 | def invert(d): |
| 227 | return dict((x[1], x[0]) for x in d.iteritems()) |
| 228 | |
Michael Ellerman | 27d318a | 2014-06-17 17:54:31 +1000 | [diff] [blame] | 229 | filters = {} |
| 230 | filters['kvm_userspace_exit'] = ('reason', invert(userspace_exit_reasons)) |
| 231 | if exit_reasons: |
| 232 | filters['kvm_exit'] = ('exit_reason', invert(exit_reasons)) |
Jan Kiszka | 626c427 | 2011-10-07 09:37:49 +0200 | [diff] [blame] | 233 | |
Michael Ellerman | 4725398 | 2014-06-17 17:54:35 +1000 | [diff] [blame] | 234 | import struct, array |
Jan Kiszka | 626c427 | 2011-10-07 09:37:49 +0200 | [diff] [blame] | 235 | |
| 236 | libc = ctypes.CDLL('libc.so.6') |
| 237 | syscall = libc.syscall |
| 238 | class perf_event_attr(ctypes.Structure): |
| 239 | _fields_ = [('type', ctypes.c_uint32), |
| 240 | ('size', ctypes.c_uint32), |
| 241 | ('config', ctypes.c_uint64), |
| 242 | ('sample_freq', ctypes.c_uint64), |
| 243 | ('sample_type', ctypes.c_uint64), |
| 244 | ('read_format', ctypes.c_uint64), |
| 245 | ('flags', ctypes.c_uint64), |
| 246 | ('wakeup_events', ctypes.c_uint32), |
| 247 | ('bp_type', ctypes.c_uint32), |
| 248 | ('bp_addr', ctypes.c_uint64), |
| 249 | ('bp_len', ctypes.c_uint64), |
| 250 | ] |
| 251 | def _perf_event_open(attr, pid, cpu, group_fd, flags): |
Heinz Graalfs | 1b3e6f8 | 2012-10-29 02:13:20 +0000 | [diff] [blame] | 252 | return syscall(sc_perf_evt_open, ctypes.pointer(attr), ctypes.c_int(pid), |
Jan Kiszka | 626c427 | 2011-10-07 09:37:49 +0200 | [diff] [blame] | 253 | ctypes.c_int(cpu), ctypes.c_int(group_fd), |
| 254 | ctypes.c_long(flags)) |
| 255 | |
| 256 | PERF_TYPE_HARDWARE = 0 |
| 257 | PERF_TYPE_SOFTWARE = 1 |
| 258 | PERF_TYPE_TRACEPOINT = 2 |
| 259 | PERF_TYPE_HW_CACHE = 3 |
| 260 | PERF_TYPE_RAW = 4 |
| 261 | PERF_TYPE_BREAKPOINT = 5 |
| 262 | |
| 263 | PERF_SAMPLE_IP = 1 << 0 |
| 264 | PERF_SAMPLE_TID = 1 << 1 |
| 265 | PERF_SAMPLE_TIME = 1 << 2 |
| 266 | PERF_SAMPLE_ADDR = 1 << 3 |
| 267 | PERF_SAMPLE_READ = 1 << 4 |
| 268 | PERF_SAMPLE_CALLCHAIN = 1 << 5 |
| 269 | PERF_SAMPLE_ID = 1 << 6 |
| 270 | PERF_SAMPLE_CPU = 1 << 7 |
| 271 | PERF_SAMPLE_PERIOD = 1 << 8 |
| 272 | PERF_SAMPLE_STREAM_ID = 1 << 9 |
| 273 | PERF_SAMPLE_RAW = 1 << 10 |
| 274 | |
| 275 | PERF_FORMAT_TOTAL_TIME_ENABLED = 1 << 0 |
| 276 | PERF_FORMAT_TOTAL_TIME_RUNNING = 1 << 1 |
| 277 | PERF_FORMAT_ID = 1 << 2 |
| 278 | PERF_FORMAT_GROUP = 1 << 3 |
| 279 | |
| 280 | import re |
| 281 | |
| 282 | sys_tracing = '/sys/kernel/debug/tracing' |
| 283 | |
| 284 | class Group(object): |
| 285 | def __init__(self, cpu): |
| 286 | self.events = [] |
| 287 | self.group_leader = None |
| 288 | self.cpu = cpu |
| 289 | def add_event(self, name, event_set, tracepoint, filter = None): |
| 290 | self.events.append(Event(group = self, |
| 291 | name = name, event_set = event_set, |
| 292 | tracepoint = tracepoint, filter = filter)) |
| 293 | if len(self.events) == 1: |
| 294 | self.file = os.fdopen(self.events[0].fd) |
| 295 | def read(self): |
| 296 | bytes = 8 * (1 + len(self.events)) |
| 297 | fmt = 'xxxxxxxx' + 'q' * len(self.events) |
| 298 | return dict(zip([event.name for event in self.events], |
| 299 | struct.unpack(fmt, self.file.read(bytes)))) |
| 300 | |
| 301 | class Event(object): |
| 302 | def __init__(self, group, name, event_set, tracepoint, filter = None): |
| 303 | self.name = name |
| 304 | attr = perf_event_attr() |
| 305 | attr.type = PERF_TYPE_TRACEPOINT |
| 306 | attr.size = ctypes.sizeof(attr) |
| 307 | id_path = os.path.join(sys_tracing, 'events', event_set, |
| 308 | tracepoint, 'id') |
| 309 | id = int(file(id_path).read()) |
| 310 | attr.config = id |
| 311 | attr.sample_type = (PERF_SAMPLE_RAW |
| 312 | | PERF_SAMPLE_TIME |
| 313 | | PERF_SAMPLE_CPU) |
| 314 | attr.sample_period = 1 |
| 315 | attr.read_format = PERF_FORMAT_GROUP |
| 316 | group_leader = -1 |
| 317 | if group.events: |
| 318 | group_leader = group.events[0].fd |
| 319 | fd = _perf_event_open(attr, -1, group.cpu, group_leader, 0) |
| 320 | if fd == -1: |
| 321 | raise Exception('perf_event_open failed') |
| 322 | if filter: |
| 323 | import fcntl |
Michael Ellerman | a15d564 | 2014-06-17 17:54:34 +1000 | [diff] [blame] | 324 | fcntl.ioctl(fd, ioctl_numbers['SET_FILTER'], filter) |
Jan Kiszka | 626c427 | 2011-10-07 09:37:49 +0200 | [diff] [blame] | 325 | self.fd = fd |
| 326 | def enable(self): |
| 327 | import fcntl |
Michael Ellerman | a15d564 | 2014-06-17 17:54:34 +1000 | [diff] [blame] | 328 | fcntl.ioctl(self.fd, ioctl_numbers['ENABLE'], 0) |
Jan Kiszka | 626c427 | 2011-10-07 09:37:49 +0200 | [diff] [blame] | 329 | def disable(self): |
| 330 | import fcntl |
Michael Ellerman | a15d564 | 2014-06-17 17:54:34 +1000 | [diff] [blame] | 331 | fcntl.ioctl(self.fd, ioctl_numbers['DISABLE'], 0) |
Jan Kiszka | 626c427 | 2011-10-07 09:37:49 +0200 | [diff] [blame] | 332 | |
| 333 | class TracepointProvider(object): |
| 334 | def __init__(self): |
| 335 | path = os.path.join(sys_tracing, 'events', 'kvm') |
| 336 | fields = [f |
| 337 | for f in os.listdir(path) |
| 338 | if os.path.isdir(os.path.join(path, f))] |
| 339 | extra = [] |
| 340 | for f in fields: |
| 341 | if f in filters: |
| 342 | subfield, values = filters[f] |
| 343 | for name, number in values.iteritems(): |
| 344 | extra.append(f + '(' + name + ')') |
| 345 | fields += extra |
| 346 | self._setup(fields) |
| 347 | self.select(fields) |
| 348 | def fields(self): |
| 349 | return self._fields |
Michael Ellerman | 763952d | 2014-06-17 17:54:30 +1000 | [diff] [blame] | 350 | |
| 351 | def _online_cpus(self): |
| 352 | l = [] |
| 353 | pattern = r'cpu([0-9]+)' |
| 354 | basedir = '/sys/devices/system/cpu' |
| 355 | for entry in os.listdir(basedir): |
| 356 | match = re.match(pattern, entry) |
| 357 | if not match: |
| 358 | continue |
| 359 | path = os.path.join(basedir, entry, 'online') |
| 360 | if os.path.exists(path) and open(path).read().strip() != '1': |
| 361 | continue |
| 362 | l.append(int(match.group(1))) |
| 363 | return l |
| 364 | |
Jan Kiszka | 626c427 | 2011-10-07 09:37:49 +0200 | [diff] [blame] | 365 | def _setup(self, _fields): |
| 366 | self._fields = _fields |
Michael Ellerman | 763952d | 2014-06-17 17:54:30 +1000 | [diff] [blame] | 367 | cpus = self._online_cpus() |
Jan Kiszka | 626c427 | 2011-10-07 09:37:49 +0200 | [diff] [blame] | 368 | import resource |
Michael Ellerman | 763952d | 2014-06-17 17:54:30 +1000 | [diff] [blame] | 369 | nfiles = len(cpus) * 1000 |
Jan Kiszka | 626c427 | 2011-10-07 09:37:49 +0200 | [diff] [blame] | 370 | resource.setrlimit(resource.RLIMIT_NOFILE, (nfiles, nfiles)) |
| 371 | events = [] |
| 372 | self.group_leaders = [] |
Michael Ellerman | 763952d | 2014-06-17 17:54:30 +1000 | [diff] [blame] | 373 | for cpu in cpus: |
Jan Kiszka | 626c427 | 2011-10-07 09:37:49 +0200 | [diff] [blame] | 374 | group = Group(cpu) |
| 375 | for name in _fields: |
| 376 | tracepoint = name |
| 377 | filter = None |
| 378 | m = re.match(r'(.*)\((.*)\)', name) |
| 379 | if m: |
| 380 | tracepoint, sub = m.groups() |
| 381 | filter = '%s==%d\0' % (filters[tracepoint][0], |
| 382 | filters[tracepoint][1][sub]) |
| 383 | event = group.add_event(name, event_set = 'kvm', |
| 384 | tracepoint = tracepoint, |
| 385 | filter = filter) |
| 386 | self.group_leaders.append(group) |
| 387 | def select(self, fields): |
| 388 | for group in self.group_leaders: |
| 389 | for event in group.events: |
| 390 | if event.name in fields: |
| 391 | event.enable() |
| 392 | else: |
| 393 | event.disable() |
| 394 | def read(self): |
| 395 | from collections import defaultdict |
| 396 | ret = defaultdict(int) |
| 397 | for group in self.group_leaders: |
| 398 | for name, val in group.read().iteritems(): |
| 399 | ret[name] += val |
| 400 | return ret |
| 401 | |
| 402 | class Stats: |
Paolo Bonzini | b763adf | 2014-05-21 12:42:26 +0200 | [diff] [blame] | 403 | def __init__(self, providers, fields = None): |
| 404 | self.providers = providers |
Jan Kiszka | 626c427 | 2011-10-07 09:37:49 +0200 | [diff] [blame] | 405 | self.fields_filter = fields |
| 406 | self._update() |
| 407 | def _update(self): |
| 408 | def wanted(key): |
| 409 | import re |
| 410 | if not self.fields_filter: |
| 411 | return True |
| 412 | return re.match(self.fields_filter, key) is not None |
Paolo Bonzini | b763adf | 2014-05-21 12:42:26 +0200 | [diff] [blame] | 413 | self.values = dict() |
| 414 | for d in providers: |
| 415 | provider_fields = [key for key in d.fields() if wanted(key)] |
| 416 | for key in provider_fields: |
| 417 | self.values[key] = None |
| 418 | d.select(provider_fields) |
Jan Kiszka | 626c427 | 2011-10-07 09:37:49 +0200 | [diff] [blame] | 419 | def set_fields_filter(self, fields_filter): |
| 420 | self.fields_filter = fields_filter |
| 421 | self._update() |
| 422 | def get(self): |
Paolo Bonzini | b763adf | 2014-05-21 12:42:26 +0200 | [diff] [blame] | 423 | for d in providers: |
| 424 | new = d.read() |
| 425 | for key in d.fields(): |
| 426 | oldval = self.values.get(key, (0, 0)) |
| 427 | newval = new[key] |
| 428 | newdelta = None |
| 429 | if oldval is not None: |
| 430 | newdelta = newval - oldval[0] |
| 431 | self.values[key] = (newval, newdelta) |
Jan Kiszka | 626c427 | 2011-10-07 09:37:49 +0200 | [diff] [blame] | 432 | return self.values |
| 433 | |
| 434 | if not os.access('/sys/kernel/debug', os.F_OK): |
| 435 | print 'Please enable CONFIG_DEBUG_FS in your kernel' |
| 436 | sys.exit(1) |
| 437 | if not os.access('/sys/kernel/debug/kvm', os.F_OK): |
| 438 | print "Please mount debugfs ('mount -t debugfs debugfs /sys/kernel/debug')" |
| 439 | print "and ensure the kvm modules are loaded" |
| 440 | sys.exit(1) |
| 441 | |
| 442 | label_width = 40 |
| 443 | number_width = 10 |
| 444 | |
| 445 | def tui(screen, stats): |
| 446 | curses.use_default_colors() |
| 447 | curses.noecho() |
| 448 | drilldown = False |
| 449 | fields_filter = stats.fields_filter |
| 450 | def update_drilldown(): |
| 451 | if not fields_filter: |
| 452 | if drilldown: |
| 453 | stats.set_fields_filter(None) |
| 454 | else: |
| 455 | stats.set_fields_filter(r'^[^\(]*$') |
| 456 | update_drilldown() |
| 457 | def refresh(sleeptime): |
| 458 | screen.erase() |
| 459 | screen.addstr(0, 0, 'kvm statistics') |
| 460 | row = 2 |
| 461 | s = stats.get() |
| 462 | def sortkey(x): |
| 463 | if s[x][1]: |
| 464 | return (-s[x][1], -s[x][0]) |
| 465 | else: |
| 466 | return (0, -s[x][0]) |
| 467 | for key in sorted(s.keys(), key = sortkey): |
| 468 | if row >= screen.getmaxyx()[0]: |
| 469 | break |
| 470 | values = s[key] |
| 471 | if not values[0] and not values[1]: |
| 472 | break |
| 473 | col = 1 |
| 474 | screen.addstr(row, col, key) |
| 475 | col += label_width |
| 476 | screen.addstr(row, col, '%10d' % (values[0],)) |
| 477 | col += number_width |
| 478 | if values[1] is not None: |
| 479 | screen.addstr(row, col, '%8d' % (values[1] / sleeptime,)) |
| 480 | row += 1 |
| 481 | screen.refresh() |
| 482 | |
| 483 | sleeptime = 0.25 |
| 484 | while True: |
| 485 | refresh(sleeptime) |
| 486 | curses.halfdelay(int(sleeptime * 10)) |
| 487 | sleeptime = 3 |
| 488 | try: |
| 489 | c = screen.getkey() |
| 490 | if c == 'x': |
| 491 | drilldown = not drilldown |
| 492 | update_drilldown() |
| 493 | if c == 'q': |
| 494 | break |
| 495 | except KeyboardInterrupt: |
| 496 | break |
| 497 | except curses.error: |
| 498 | continue |
| 499 | |
| 500 | def batch(stats): |
| 501 | s = stats.get() |
| 502 | time.sleep(1) |
| 503 | s = stats.get() |
| 504 | for key in sorted(s.keys()): |
| 505 | values = s[key] |
| 506 | print '%-22s%10d%10d' % (key, values[0], values[1]) |
| 507 | |
| 508 | def log(stats): |
| 509 | keys = sorted(stats.get().iterkeys()) |
| 510 | def banner(): |
| 511 | for k in keys: |
| 512 | print '%10s' % k[0:9], |
| 513 | print |
| 514 | def statline(): |
| 515 | s = stats.get() |
| 516 | for k in keys: |
| 517 | print ' %9d' % s[k][1], |
| 518 | print |
| 519 | line = 0 |
| 520 | banner_repeat = 20 |
| 521 | while True: |
| 522 | time.sleep(1) |
| 523 | if line % banner_repeat == 0: |
| 524 | banner() |
| 525 | statline() |
| 526 | line += 1 |
| 527 | |
| 528 | options = optparse.OptionParser() |
| 529 | options.add_option('-1', '--once', '--batch', |
| 530 | action = 'store_true', |
| 531 | default = False, |
| 532 | dest = 'once', |
| 533 | help = 'run in batch mode for one second', |
| 534 | ) |
| 535 | options.add_option('-l', '--log', |
| 536 | action = 'store_true', |
| 537 | default = False, |
| 538 | dest = 'log', |
| 539 | help = 'run in logging mode (like vmstat)', |
| 540 | ) |
Paolo Bonzini | b763adf | 2014-05-21 12:42:26 +0200 | [diff] [blame] | 541 | options.add_option('-t', '--tracepoints', |
| 542 | action = 'store_true', |
| 543 | default = False, |
| 544 | dest = 'tracepoints', |
| 545 | help = 'retrieve statistics from tracepoints', |
| 546 | ) |
| 547 | options.add_option('-d', '--debugfs', |
| 548 | action = 'store_true', |
| 549 | default = False, |
| 550 | dest = 'debugfs', |
| 551 | help = 'retrieve statistics from debugfs', |
| 552 | ) |
Jan Kiszka | 626c427 | 2011-10-07 09:37:49 +0200 | [diff] [blame] | 553 | options.add_option('-f', '--fields', |
| 554 | action = 'store', |
| 555 | default = None, |
| 556 | dest = 'fields', |
| 557 | help = 'fields to display (regex)', |
| 558 | ) |
| 559 | (options, args) = options.parse_args(sys.argv) |
| 560 | |
Paolo Bonzini | b763adf | 2014-05-21 12:42:26 +0200 | [diff] [blame] | 561 | providers = [] |
| 562 | if options.tracepoints: |
| 563 | providers.append(TracepointProvider()) |
| 564 | if options.debugfs: |
| 565 | providers.append(DebugfsProvider()) |
Jan Kiszka | 626c427 | 2011-10-07 09:37:49 +0200 | [diff] [blame] | 566 | |
Paolo Bonzini | b763adf | 2014-05-21 12:42:26 +0200 | [diff] [blame] | 567 | if len(providers) == 0: |
| 568 | try: |
| 569 | providers = [TracepointProvider()] |
| 570 | except: |
| 571 | providers = [DebugfsProvider()] |
| 572 | |
| 573 | stats = Stats(providers, fields = options.fields) |
Jan Kiszka | 626c427 | 2011-10-07 09:37:49 +0200 | [diff] [blame] | 574 | |
| 575 | if options.log: |
| 576 | log(stats) |
| 577 | elif not options.once: |
| 578 | import curses.wrapper |
| 579 | curses.wrapper(tui, stats) |
| 580 | else: |
| 581 | batch(stats) |