Skip to content

Commit

Permalink
Merge pull request TritonDataCenter#4 from ackalker/add-stap-support
Browse files Browse the repository at this point in the history
Add support for SystemTap profile script output, add process name prefix
  • Loading branch information
davepacheco committed Nov 20, 2012
2 parents cc6c113 + 9ac49ca commit be2d8bf
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 2 deletions.
20 changes: 20 additions & 0 deletions cmd/stackcollapse-stap
@@ -0,0 +1,20 @@
#!/usr/bin/env node

/*
* cmd/stackcollapse: emit collapsed stack traces from stap output
*/

var mod_bunyan = require('bunyan');
var mod_stackvis = require('../lib/stackvis');

var log = new mod_bunyan({
'name': 'stackcollapse',
'stream': process.stderr
});

var reader = mod_stackvis.readerLookup('stap');
var writer = mod_stackvis.writerLookup('collapsed');

mod_stackvis.pipeStacks(log, process.stdin, reader, writer, process.stdout,
function () {});
process.stdin.resume();
14 changes: 12 additions & 2 deletions lib/input-perf.js
Expand Up @@ -25,6 +25,7 @@ function PerfStreamReader(input, log)
{
this.dsr_log = log;
this.dsr_linenum = 0;
this.dsr_prefix = '';
this.dsr_stack = [];
this.dsr_carrier = mod_carrier.carry(input);
this.dsr_carrier.on('line', this.onLine.bind(this));
Expand All @@ -43,9 +44,12 @@ PerfStreamReader.prototype.onLine = function (line)
if (/^#/.exec(line))
return;

/* Skip summary lines */
if (/^\S+/.exec(line))
/* Get process name from summary line, to use as prefix */
var match = /(^\w+)\s+/.exec(line);
if (match) {
this.dsr_prefix = match[1];
return;
}

/*
* In general, lines may have leading or trailing whitespace and the
Expand All @@ -68,6 +72,7 @@ PerfStreamReader.prototype.onLine = function (line)
}

this.emit('stack', this.dsr_stack, 1);
this.dsr_prefix = '';
this.dsr_stack = [];
return;
}
Expand All @@ -90,6 +95,11 @@ PerfStreamReader.prototype.onLine = function (line)
return;
}

/* Add prefix */
if (this.dsr_prefix.length > 0) {
frame = this.dsr_prefix + '`' + frame;
}

this.dsr_stack.unshift(frame);
};

Expand Down
145 changes: 145 additions & 0 deletions lib/input-stap.js
@@ -0,0 +1,145 @@
/*
* lib/input-stap.js: reads output from a stap profiling script, which emits
* stanzas that look like this:
*
* ubt["bar+0x32 [foo]
* foo+0x57 [foo]
* main+0x48 [foo]
* __libc_start_main+0xed [libc-2.15.so]
* _start+0x29 [foo]"]=0x77
*
* You can generate such output with:
*
* # stap \
* -e "global ubt; \
* probe timer.profile { ubt[sprint_ubacktrace()] += 1 }; \
* probe timer.s(30) { exit() }" \
* -o stap.out
*
* If stap warns about missing unwind data for a module, and stap
* suggests adding '-d /lib/libquux.so', which you know to be a shared
* library used by the 'foo' binary, add the following to the above
* command:
*
* -d /path/to/foo $(ldd /path/to/foo | awk 'NF==4 { print "-d", $3 }')
*
* to deal with all warnings related to shared libraries used by 'foo',
* all at once.
*/

var mod_util = require('util');
var mod_events = require('events');

var mod_carrier = require('carrier');

exports.reader = PerfStreamReader;

function PerfStreamReader(input, log)
{
this.dsr_log = log;
this.dsr_linenum = 0;
this.dsr_addingframes = false;
this.dsr_prefixes = [];
this.dsr_stack = [];
this.dsr_carrier = mod_carrier.carry(input);
this.dsr_carrier.on('line', this.onLine.bind(this));
this.dsr_carrier.on('end', this.onEnd.bind(this));

mod_events.EventEmitter.call(this);
}

mod_util.inherits(PerfStreamReader, mod_events.EventEmitter);

PerfStreamReader.prototype.onLine = function (line)
{
++this.dsr_linenum;

var match;
if (!this.dsr_addingframes) {
/* Skip array name */
line.replace(/^\w+\[/, '');

/* Find and add prefixes */
while (true) {
/* JSSTYLED */
match = /(?:"([^"]*)",)(.*$)/.exec(line);
if (!match)
break;
this.dsr_prefixes.push(match[1]);
line = match[2];
}

/* Find first frame */
/* JSSTYLED */
match = /(?:"(.*$))/.exec(line);
if (!match) {
this.dsr_log.warn('line ' + this.dsr_linenum +
': no first frame found');
return;
}
line = match[1];
this.dsr_addingframes = true;
}

/* Look for count */
var count;
/* JSSTYLED */
match = /(^.*)"\]=(\w+$)/.exec(line);
if (match) {
line = match[1];
count = parseInt(match[2], 16);
this.dsr_addingframes = false;
}

/*
* In general, frames have one of the following sets of components:
*
* address
* address [module+offset]
* function+offset [module]
*
* We try to avoid assuming too much about the form in order to support
* various annotations provided by ustack helpers.
*/
var frame = line;
frame = frame.replace(/ \[(\S+)\]$/, '');
/* JSSTYLED */
frame = frame.replace(/\+.*/, '');

/*
* Remove both function and template parameters from demangled C++
* frames, but skip the first two characters because they're used by the
* Node.js ustack helper as separators.
*/
/* JSSTYLED */
frame = frame.replace(/(..)[(<].*/, '$1');

if (line.length === 0) {
if (this.dsr_stack.length !== 0)
this.dsr_log.warn('line ' + this.dsr_linenum +
': unexpected blank line');
return;
}

/* Add prefixes */
if (this.dsr_prefixes.length > 0) {
frame = this.dsr_prefixes.join('`') + '`' + frame;
}

this.dsr_stack.unshift(frame);

if (!this.dsr_addingframes) {
this.emit('stack', this.dsr_stack, count);
this.dsr_prefixes = [];
this.dsr_stack = [];
}
};

PerfStreamReader.prototype.onEnd = function ()
{
if (this.dsr_stack.length !== 0)
this.dsr_log.warn('line ' + this.dsr_linenum +
': unexpected end of stream');

this.emit('end');
};
2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -6,6 +6,8 @@
"bin": {
"flamegraph": "./cmd/flamegraph",
"stackcollapse": "./cmd/stackcollapse",
"stackcollapse-perf": "./cmd/stackcollapse-perf",
"stackcollapse-stap": "./cmd/stackcollapse-stap",
"stackvis": "./cmd/stackvis"
},

Expand Down

0 comments on commit be2d8bf

Please sign in to comment.