Statistics
| Branch: | Revision:

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
        print
463
    def statline():
464
        s = stats.get()
465
        for k in keys:
466
            print ' %9d' % s[k][1],
467
        print
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)