diff --git a/README.md b/README.md index 35acf11..c9130ec 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,12 @@ postprocess it: thoth: waiting for completion of job da3c0bf5-b04f-445b-aee7-af43ea3d17c0 thoth: job da3c0bf5-b04f-445b-aee7-af43ea3d17c0 completed in 0h0m2s +To upload the program that generated the core or crash dump, pass the `-p` argument. This will associate the program file with the core or crash dump and load it in MDB whenever `debug` is executed. + +``` +$ thoth upload core.19972 node +``` + ### info Returns the JSON blob associated with the specified dump. @@ -158,7 +164,22 @@ an exit status of 2 denotes that the dump was not found. ### debug Results in an interactive debugging session debugging the specified dump -via [mlogin](http://blog.sysmgr.org/2013/06/manta-mlogin.html). +via [mlogin](http://blog.sysmgr.org/2013/06/manta-mlogin.html). For example, +to debug a core dump specied by the object value of `3e166b93871e7747c799008f58bd30b9` +run the following: + +``` + $ thoth debug 3e166b93871e7747c799008f58bd30b9 +``` + +The `debug` command supports passing a binary referenced by a manta path. This is useful +when you need to analyze a core dump generated by a different version of node. The option is +`-p` and should be the manta path of the binary to pass into MDB. The example below demonstrates +passing the binary stored at /Joyent_Dev/public/node-6 into MDB. + +``` + $ thoth debug 3e166b93871e7747c799008f58bd30b9 -p /Joyent_Dev/public/node-6 +``` ### ls @@ -172,7 +193,7 @@ By default, the dumps are listed in time order from oldest to newest. For a given local dump, provides the hashed name of the object. $ thoth object core.19972 - 3e166b93871e7747c799008f58bd30b9 + This can be used to automate uploads of dumps. @@ -300,7 +321,7 @@ Here's an analyzer that sets an ```fmri``` property to be that of the echo $THOTH_NAME: $fmri The output of analyzers is aggregated and displayed upon completion -of ```analyze```. +of ```analyze```. #### Debugging analyzers @@ -319,7 +340,7 @@ specify both the dump and the analyzer: This results in an interactive shell whereby one can interactively edit the specified analyzer by editing the file referred to by -```$THOTH_ANALYZER``` and can test the analyzer by running +```$THOTH_ANALYZER``` and can test the analyzer by running ```thoth_analyze```. When the shell exits successfully (that is, an exit of 0), the contents of the file pointed to by ```$THOTH_ANALYZER``` will be written to the specified analyzer. diff --git a/bin/thoth b/bin/thoth index e63e21a..d21a92d 100755 --- a/bin/thoth +++ b/bin/thoth @@ -1280,6 +1280,64 @@ var putCore = function (client, file, stat, base, cb) }); }; +var processProgram = function (client, base, program, cb) +{ + var tmp = '/tmp/tmp.json'; + var json = '/tmp/new.json'; + + var cmd = 'cat > ' + tmp + ' < ' + + json + ';'; + cmd += 'mput -f ' + json + ' $MANTA_INPUT_OBJECT ;'; + cmd += jobExecThoth(json) + ' || ' + + 'mln $MANTA_INPUT_OBJECT ' + thoth.path + '/' + + '`basename $(dirname $MANTA_INPUT_OBJECT)`' + thoth.unindexed; + + jobOneKey(client, mod_path.join(base, '/info.json'), cmd, cb, jobSetThoth); +} + +var uncompressProgram = function (client, stat, base, program, cb) +{ + cmd = 'gunzip -cd $MANTA_INPUT_FILE > /var/tmp/gunzip.$$ ;' + cmd += 'mput -f /var/tmp/gunzip.$$ ' + program; + + status('creating job to uncompress ' + mod_path.basename(base)); + jobOneKey(client, program + '.gz', cmd, + function () { processProgram(client, base, program, cb); }, + function (job) { job.phases[0].disk = 128; }); +}; + +var putProgram = function (client, file, stat, base, cb) +{ + var stream = mod_fs.createReadStream(file); + var program = base + '/' + mod_path.basename(file); + var gzip = mod_zlib.createGzip(); + + status('uploading ' + mod_path.basename(program) + ' to ' + + mod_path.basename(base)); + + progressBar(program, stream, stat); + + pipe = stream.pipe(gzip); + pipe.pause(); + + var opts = { + copies: 1, + headers: { 'max-content-length': stat.size } + }; + + stream.on('open', function () { + client.put(program + '.gz', gzip, opts, function (err) { + checkError(err); + + uncompressProgram(client, stat, base, program, cb); + }); + }); +}; + var processCrash = function (client, stat, base, dump, cb) { var mdb = function (cmd) { @@ -1424,6 +1482,29 @@ var openDump = function (name, cb, failed) } } +var openProgram = function (name, cb, failed) +{ + var fd, file = { name: name }; + var stat; + var sum = mod_crypto.createHash('md5'); + var path = [ thoth.path ], digest; + + try { + fd = mod_fs.openSync(name, 'r'); + } catch (err) { + if (!failed) + fatal('couldn\'t open "' + name + '": ' + err); + return (failed()); + } + + file.stat = mod_fs.fstatSync(fd); + + mod_fs.closeSync(fd); + path.push(file.digest = sum.digest('hex')); + file.base = path.join('/'); + cb(file); +} + var infoGet = function (client, dump, cb, bypass) { argToInfo(client, dump, function (object, err, stream, res) { @@ -1446,19 +1527,36 @@ var infoGet = function (client, dump, cb, bypass) }, bypass); } -handlers.upload = function (client, argv) +handlers.upload = function (client, argv, program) { put = { crash: putCrash, core: putCore }; - var next = function () { + var pArgIndex = argv.length > 2 ? argv.indexOf('-p') : -1; + if (pArgIndex !== -1) { + program = argv[pArgIndex + 1]; + argv.splice(pArgIndex, 2); + } + + var finish = function () { if (argv.length == 1) process.exit(0); - handlers.upload(client, argv.slice(1, argv.length)); - }; + handlers.upload(client, argv.slice(1, argv.length), program); + } + + var next = function (dump) { + if (!program) { + return finish(); + } + + openProgram(program, function (file) { + status('creating program ' + file.digest); + putProgram(client, file.name, file.stat, dump.base, finish); + }); + } openDump(argv[0], function (file) { status('creating ' + file.digest); @@ -1466,7 +1564,9 @@ handlers.upload = function (client, argv) client.mkdirp(file.base, function (err) { checkError(err); put[file.type](client, - file.name, file.stat, file.base, next); + file.name, file.stat, file.base, function () { + next(file); + }); }); }); } @@ -1696,6 +1796,7 @@ handlers.debug = function (client, argv) var mlogin = mod_path.dirname(require.resolve('manta')) + '/../bin/mlogin'; var analyzer = undefined; + var nodePath = undefined; var analyzerCmd = function (args) { var cmd = 'export THOTH_ANALYZER=/var/tmp/' + analyzer + '\n'; @@ -1758,6 +1859,8 @@ handlers.debug = function (client, argv) if (analyzer) { cmd += analyzerCmd(args); + } else if (nodePath || dump.program) { + cmd += 'mdb /assets' + (nodePath || dump.program) + ' $MANTA_INPUT_FILE'; } else { cmd += 'mdb $MANTA_INPUT_FILE'; } @@ -1768,6 +1871,12 @@ handlers.debug = function (client, argv) '-c', '/assets/' + asset, '--memory=2048' ]); + if (nodePath) { + args = args.concat(['-s', nodePath]); + } else if (dump.program) { + args = args.concat(['-s', dump.program]); + } + var child = mod_child.spawn(mlogin, args, { stdio: 'inherit' }); @@ -1782,6 +1891,12 @@ handlers.debug = function (client, argv) }, { loadv8: analyzer ? false : true }); }; + var nodeIndex = argv.length > 2 ? argv.indexOf('-p') : -1; + if (nodeIndex !== -1) { + nodePath = argv[nodeIndex + 1]; + argv.splice(nodeIndex, 2); + } + if (argv.length > 1 && argv[argv.length - 1].indexOf('=') == -1) analyzer = argv.pop();