Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for SystemTap profile script output, add process name prefix #4

Merged
merged 3 commits into from Nov 20, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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