Created
May 4, 2015 21:11
-
-
Save jclulow/9aa76671a258f724f4e0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* This utility uses "mdb -k" to dump out the KVM ring buffer | |
* for a particular VM. You must run it _BEFORE_ you kill or | |
* restart the qemu process. Output is to stdout, so direct | |
* that to a file -- it will be about ~500KB of JSON for a large | |
* VM. | |
* | |
* To run this: | |
* | |
* on SDC 6.X: | |
* /usr/bin/node dump_ring_buffer.js $QEMU_PID \ | |
* > out.json | |
* | |
* on SDC 7+: | |
* /usr/node/bin/node dump_ring_buffer.js $QEMU_PID \ | |
* > out.json | |
* | |
*/ | |
var mod_child_process = require('child_process'); | |
var mod_util = require('util'); | |
var mod_crypto = require('crypto'); | |
var mod_events = require('events'); | |
var START_MARKER = '## 8b90d457-479d-4619-9b75-8407edb83cc5'; | |
var END_MARKER = '## 2c37852b-3639-4298-ab18-1d4c6a5aa344'; | |
var MARKER_REGEX = new RegExp(START_MARKER + ' ?\\n((?:.|[\\r\\n])*)' + | |
END_MARKER + ' ?\\n', 'm'); | |
function | |
MDB(inputfile) | |
{ | |
var self = this; | |
mod_events.EventEmitter.call(self); | |
self._exit = null; | |
self._qq = null; | |
self._q = []; | |
self._inputfile = inputfile; | |
self._proc = mod_child_process.spawn('/usr/bin/mdb', | |
[ inputfile ]); | |
self._data = ''; | |
self._proc.stdout.on('data', function (ch) { | |
self._data += ch.toString('utf8'); | |
self._handle(); | |
}); | |
self._proc.stderr.on('data', function (ch) { | |
self.emit('stderr', ch); | |
}); | |
self._proc.once('exit', function(code, signal) { | |
self._exit = { code: code, signal: signal }; | |
if (self._qq === null && self._q.length === 0) | |
self.emit('exit', code, signal); | |
}); | |
/* | |
* Send a dummy command to separate any Banner noise | |
* from actual output: | |
*/ | |
self.command(null, null, function () { | |
self._started = true; | |
}); | |
} | |
mod_util.inherits(MDB, mod_events.EventEmitter); | |
MDB.prototype._dispatch = function | |
_dispatch() | |
{ | |
var self = this; | |
if (self._qq !== null || self._exit != null || self._q.length === 0) | |
return; | |
self._qq = self._q.shift(); | |
self._proc.stdin.write('::echo ' + START_MARKER + '\n'); | |
if (self._qq.command) | |
self._proc.stdin.write(self._qq.command + '\n'); | |
self._proc.stdin.write('::echo ' + END_MARKER + '\n'); | |
} | |
MDB.prototype._handle = function | |
_handle() | |
{ | |
var self = this; | |
var m = MARKER_REGEX.exec(self._data); | |
if (!m) | |
return; | |
/* | |
* Throw away the data we just consumed. | |
*/ | |
self._data = self._data.substr(m.index + m[0].length); | |
/* | |
* Send the result to the callback: | |
*/ | |
if (self._qq) { | |
if (self._qq.callback) { | |
self._qq.callback(self._qq.command, self._qq.argument, | |
m[1]); | |
} | |
self._qq = null; | |
} | |
if (self._q.length === 0 && self._exit) { | |
self.emit('exit', self._exit.code, self._exit.signal); | |
return; | |
} | |
/* | |
* Attempt to send the next command to mdb: | |
*/ | |
self._dispatch(); | |
} | |
MDB.prototype.command = function | |
command(cmdstr, argument, callback) | |
{ | |
var self = this; | |
if (self._exit) { | |
throw new (Error('attempt to run command after mdb already ' + | |
'exited')); | |
} | |
self._q.push({ | |
command: cmdstr, | |
argument: argument, | |
callback: callback | |
}); | |
process.nextTick(function() { | |
self._dispatch(); | |
}); | |
} | |
MDB.prototype.close = function | |
close() | |
{ | |
var self = this; | |
self._proc.stdin.write('::quit\n'); | |
self._proc.stdin.end(); | |
} | |
module.exports = { | |
MDB: MDB | |
}; | |
/* | |
* Main Routine: | |
*/ | |
var PID = process.argv[2]; | |
var KVM_T = null; | |
if (!PID) { | |
console.error('Usage: dump_ring_buffer.js <qemu_pid>'); | |
process.exit(1); | |
} | |
var TAGS = [ | |
'', | |
'CTXSAVE', | |
'CTXRESTORE', | |
'VMPTRLD', | |
'VCPUMIGRATE', | |
'VCPUCLEAR', | |
'VCPULOAD', | |
'VCPUPUT', | |
'RELOAD', | |
'EMUFAIL0', | |
'EMUFAIL1', | |
'EMUFAIL2' | |
]; | |
var CPULIST = []; | |
var M = new MDB('-k'); | |
M.command('::walk kvm | ::printf "%x %d\\n" kvm_t . kvm_pid', {}, hdlr0); | |
function | |
to_lines(instr) | |
{ | |
var lines = instr.split(/\n/); | |
if (!lines[lines.length - 1].trim()) | |
lines.splice(lines.length - 1, 1); | |
return (lines); | |
} | |
function | |
hdlr0(command, argument, output) | |
{ | |
var lines = to_lines(output); | |
for (var i = 0; i < lines.length; i++) { | |
var m = lines[i].match(/([0-9a-f]+)\s+([0-9]+)/); | |
if (!m) | |
continue; | |
if (m[2] === PID) { | |
KVM_T = m[1]; | |
break; | |
} | |
} | |
if (!KVM_T) { | |
console.error('could not find kvm_t for qemu pid %d', PID); | |
process.exit(1); | |
} | |
//console.error('kvm_t @ %s', KVM_T); | |
M.command(KVM_T + '::print kvm_t vcpus | ::array uint64_t 0t64 |' + | |
' ::eval ./J', {}, hdlr1); | |
} | |
function | |
hdlr1(command, argument, output) | |
{ | |
var lines = to_lines(output); | |
for (var i = 0; i < lines.length; i++) { | |
var m = lines[i].match(/0x([0-9a-f]+):\s+([0-9a-f]+)/); | |
if (!m) | |
continue; | |
if (m[2] !== '0') { | |
CPULIST.push({ | |
index: i, | |
vcpu_t: m[2], | |
tagcount: {}, | |
ringbuf: [] | |
}); | |
} | |
} | |
proc_cpus(); | |
} | |
var CURCPU = -1; | |
function | |
proc_cpus() | |
{ | |
if (++CURCPU >= CPULIST.length) { | |
cleanup(); | |
return; | |
} | |
var vcpu_t = CPULIST[CURCPU].vcpu_t; | |
M.command(vcpu_t + '::print kvm_vcpu_t' + | |
' kvcpu_ringbuf.kvmr_tagcount |' + | |
' ::array uint32_t 12 | ::eval ./U', {}, hdlr_pc); | |
function hdlr_pc(command, argument, output) { | |
var lines = to_lines(output); | |
for (var i = 0; i < lines.length; i++) { | |
var m = lines[i].match( | |
/0x([0-9a-f]+):\s+([0-9]+)/); | |
if (!m) | |
continue; | |
if (i > 0 && i < TAGS.length) { | |
var num = Number(m[2]); | |
CPULIST[CURCPU].tagcount[TAGS[i]] = num; | |
} | |
} | |
M.command(vcpu_t + '::print -a kvm_vcpu_t ' + | |
'kvcpu_ringbuf.kvmr_buf | ::array ' + | |
'kvm_ringbuf_entry_t 0t512 | ::printf ' + | |
'"%p %u %u %x %x %x\\n" kvm_ringbuf_entry_t ' + | |
'. kvmre_tag kvmre_cpuid kvmre_thread kvmre_tsc ' + | |
'kvmre_payload', {}, hdlr_rb); | |
} | |
function hdlr_rb(command, argument, output) { | |
var lines = to_lines(output); | |
for (var i = 0; i < lines.length; i++) { | |
var RE_HEX = '([0-9a-f]+)'; | |
var RE_INT = '([0-9]+)'; | |
var RE_SPC = '\\s+'; | |
var RE = [ RE_HEX, RE_INT, RE_INT, RE_HEX, RE_HEX, | |
RE_HEX ].join(RE_SPC); | |
var m = lines[i].match(new RegExp(RE)); | |
if (!m) | |
continue; | |
CPULIST[CURCPU].ringbuf.push({ | |
addr: m[1], | |
tag: TAGS[Number(m[2])] || m[2], | |
cpuid: Number(m[3]), | |
thread: m[4], | |
tsc: m[5], | |
payload: m[6] | |
}); | |
} | |
process.nextTick(proc_cpus); | |
} | |
} | |
function | |
cleanup() | |
{ | |
console.log(JSON.stringify(CPULIST)); | |
M.close(); | |
process.exit(0); | |
} | |
/* vim: set noet ts=8 sts=8 sw=8: */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment